JAXB створює контекст і вартість маршалерів


120

Питання трохи теоретичне, яка вартість створення контексту JAXB, маршалера та безмаркетингу?

Я виявив, що мій код може виграти від того, щоб зберігати той самий контекст JAXB і, можливо, той самий маршаллер для всіх операцій маршалінгу, а не створювати контекст і маршаллер для кожного маршалінгу.

Отже, яка вартість створення контексту JAXB та маршаллера / unmarshaller? Чи добре створити контекст + маршаллер для кожної операції з марширування або краще цього уникати?

Відповіді:


244

Примітка. Я є керівником EclipseLink JAXB (MOXy) та членом експертної групи JAXB 2 ( JSR-222 ).

JAXBContextє безпечним для потоків і повинен бути створений лише один раз та повторно використаний, щоб уникнути витрат на ініціалізацію метаданих кілька разів. Marshallerі Unmarshallerне є безпечними для потоків, але вони легкі для створення та можуть бути створені за одну операцію.


7
чудова відповідь. Я можу бути впевнений зараз, виходячи з вашого досвіду роботи на JAXB.
Володимир

7
Я вам довіряю, але чи це можна знайти десь у документації?
Гурда

3
Це задокументовано для RI: jaxb.java.net/guide/Performance_and_thread_safety.html (але не Moxy AFAIK)
Caoilte

39
Будь ласка, вкажіть це в Javadoc. Недоцільно, щоб ці важливі аспекти були недокументовані.
Thomas W

6
У Javadoc не згадується, що JAXBContext є безпечним для потоків. І майже в кожному прикладі використання JAXB не згадується, що його слід створити один раз і поділити по потоках. Як результат, зараз я виймаю ще один витік ресурсу з живої системи. Grrrrrrr.
Рег Віттон

42

В ідеалі, у вас повинні бути однократні JAXBContextта локальні екземпляри Marshallerта Unmarshaller.

JAXBContextекземпляри безпечні для потоків, Marshallerа Unmarshallerінстанції не є безпечними для потоків і ніколи не повинні ділитися між потоками.


дякую за відповідь. На жаль, я маю вибрати лише одну відповідь :-)
Володимир

15

Шкода, що це не спеціально описано в javadoc. Що я можу сказати, це те, що Spring використовує глобальний JAXBContext, який поділяється між потоками, тоді як він створює новий маршаллер для кожної операції з маршалуванням, з коментарем javadoc в коді, який говорить про те, що маршальники JAXB не обов'язково захищають потоки.

Те саме сказано на цій сторінці: https://javaee.github.io/jaxb-v2/doc/user-guide/ch03.html#other-miscellaneous-topics-performance-and-thread-safety .

Я б здогадався, що створення JAXBContext - це дорога операція, оскільки вона включає сканування класів та пакетів для анотацій. Але виміряти це найкращий спосіб знати.


Привіт @JB, чудова відповідь, особливо на ваші коментарі щодо вимірювання та чому JAXBContext коштує дорого.
Володимир

1
Javadoc завжди був слабким у вирішальних фактах життєвого циклу . Це, безумовно, дає нам тривіальне повторення власників та сеттерів, але як знати, як / де отримати або створити екземпляр, мутацію та безпеку потоків .., здається, цілком пропускають ці найважливіші фактори. Зітхніть :)
Thomas W

4

У розділі "4.2 JAXBКонтекст" JAXB 2.2 ( JSR-222 ) сказано так:

Щоб уникнути накладних витрат, створених для створення JAXBContextекземпляра, додатку JAXB рекомендується повторно використовувати екземпляр JAXBContext . Реалізація абстрактного класу JAXBContext необхідна, щоб бути безпечною для потоків , тому декілька потоків у програмі можуть спільно використовувати один і той же екземпляр JAXBContext.

[..]

Клас JAXBContext розроблений так, щоб бути незмінним і, таким чином, безпечним. Зважаючи на кількість динамічної обробки, яка потенційно може відбутися при створенні нового примірника JAXBContxt, рекомендується, щоб екземпляр JAXBContext був розподілений між потоками та використовувався якомога більше для підвищення продуктивності програми.

На жаль, специфікація не пред'являє жодних претензій щодо безпеки потоку Unmarshallerта Marshaller. Тож найкраще припустити, що їх немає.


3

Я вирішив цю проблему за допомогою:

  • безпечний загальний потік JAXBContext і локальний un / mchaller
  • (теоретично так що буде стільки екземплярів / marshaller екземплярів, скільки є ниток, які звертаються до них)
  • з синхронізацією тільки при ініціалізації un / marshaller .
public class MyClassConstructor {
    private final ThreadLocal<Unmarshaller> unmarshallerThreadLocal = new ThreadLocal<Unmarshaller>() {
        protected synchronized Unmarshaller initialValue() {
            try {
                return jaxbContext.createUnmarshaller();
            } catch (JAXBException e) {
                throw new IllegalStateException("Unable to create unmarshaller");
            }
        }
    };
    private final ThreadLocal<Marshaller> marshallerThreadLocal = new ThreadLocal<Marshaller>() {
        protected synchronized Marshaller initialValue() {
            try {
                return jaxbContext.createMarshaller();
            } catch (JAXBException e) {
                throw new IllegalStateException("Unable to create marshaller");
            }
        }
    };

    private final JAXBContext jaxbContext;

    private MyClassConstructor(){
        try {
            jaxbContext = JAXBContext.newInstance(Entity.class);
        } catch (JAXBException e) {
            throw new IllegalStateException("Unable to initialize");
        }
    }
}

8
ThreadLocal внесе інші субтильні проблеми, без вигоди. Просто збережіть один JAXBContext (це дорога частина) та створіть новий Unmarshaller, коли потрібно.
ymajoros

2

Навіть краще!! На основі хорошого рішення з публікації вище, створіть контекст лише один раз у конструкторі та збережіть його замість класу.

Замініть рядок:

  private Class clazz;

з цим:

  private JAXBContext jc;

І головний конструктор з цим:

  private Jaxb(Class clazz)
  {
     this.jc = JAXBContext.newInstance(clazz);
  }

тому в getMarshaller / getUnmarshaller ви можете видалити цей рядок:

  JAXBContext jc = JAXBContext.newInstance(clazz);

Це поліпшення робить у моєму випадку, що час обробки падає з 60 ~ 70мс до всього 5 ~ 10мс


Наскільки великий був файл XML, який ви розбирали. Чи бачите ви істотне покращення із дуже великими файлами xml?
Джон

1
насправді це не великі файли xml (міна йде від всього 2-3 кбіт до + 6 Мб), але натомість справа у великій кількості файлів xml (ми говоримо тут про близько 10000 xml запитів на хвилину); у такому випадку створення контексту, щойно набирає цих маленьких мс, робить величезну
зміну

1

Я зазвичай вирішую подібні проблеми за допомогою ThreadLocalкласового малюнка. Зважаючи на той факт, що вам потрібен різний маршалок для кожного класу, ви можете комбінувати його з singletonмалюнком -map.

Щоб заощадити 15 хвилин роботи. Тут випливає моє реалізація безпечної нитки заводу для Jaxb Marshallers та Unmarshallers.

Це дозволяє отримати доступ до примірників наступним чином ...

Marshaller m = Jaxb.get(SomeClass.class).getMarshaller();
Unmarshaller um = Jaxb.get(SomeClass.class).getUnmarshaller();

А код, який вам знадобиться, - це невеликий клас Jaxb, який виглядає так:

public class Jaxb
{
  // singleton pattern: one instance per class.
  private static Map<Class,Jaxb> singletonMap = new HashMap<>();
  private Class clazz;

  // thread-local pattern: one marshaller/unmarshaller instance per thread
  private ThreadLocal<Marshaller> marshallerThreadLocal = new ThreadLocal<>();
  private ThreadLocal<Unmarshaller> unmarshallerThreadLocal = new ThreadLocal<>();

  // The static singleton getter needs to be thread-safe too, 
  // so this method is marked as synchronized.
  public static synchronized Jaxb get(Class clazz)
  {
    Jaxb jaxb =  singletonMap.get(clazz);
    if (jaxb == null)
    {
      jaxb = new Jaxb(clazz);
      singletonMap.put(clazz, jaxb);
    }
    return jaxb;
  }

  // the constructor needs to be private, 
  // because all instances need to be created with the get method.
  private Jaxb(Class clazz)
  {
     this.clazz = clazz;
  }

  /**
   * Gets/Creates a marshaller (thread-safe)
   * @throws JAXBException
   */
  public Marshaller getMarshaller() throws JAXBException
  {
    Marshaller m = marshallerThreadLocal.get();
    if (m == null)
    {
      JAXBContext jc = JAXBContext.newInstance(clazz);
      m = jc.createMarshaller();
      marshallerThreadLocal.set(m);
    }
    return m;
  }

  /**
   * Gets/Creates an unmarshaller (thread-safe)
   * @throws JAXBException
   */
  public Unmarshaller getUnmarshaller() throws JAXBException
  {
    Unmarshaller um = unmarshallerThreadLocal.get();
    if (um == null)
    {
      JAXBContext jc = JAXBContext.newInstance(clazz);
      um = jc.createUnmarshaller();
      unmarshallerThreadLocal.set(um);
    }
    return um;
  }
}

10
ThreadLocal внесе інші субтильні проблеми, без вигоди. Просто збережіть один JAXBContext (це дорога частина) та створіть новий Unmarshaller, коли потрібно.
ymajoros

Насправді вам не потрібні окремі JAXBContexts, оскільки ви можете проходити в декількох класах. Отже, якщо ви зможете передбачити, які класи будуть підлягати маршалу, ви можете створити єдиний спільний. Крім того, специфікація JAXB вимагає, щоб вони вже були безпечними для потоків.
MauganRa
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.