JAXB создает контекст и затраты на маршаллеры

120

Вопрос немного теоретический, какова стоимость создания JAXB context, marshaller и unmarshaller?

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

Итак, какова стоимость создания контекста JAXB и маршаллера / демаршаллера? Можно ли создавать контекст + маршаллер для каждой операции маршалинга или лучше этого избегать?

Владимир
источник

Ответы:

244

Примечание. Я руководитель EclipseLink JAXB (MOXy) и участник JAXB 2 ( JSR-222 экспертной группы ).

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

bdoughan
источник
7
отличный ответ. Теперь я могу быть уверен, основываясь на вашем опыте руководителя по JAXB.
Владимир
7
Я вам доверяю, но есть ли это где-нибудь в документации?
Hurda
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 Nizet
источник
Привет @JB, отличный ответ, особенно ваши комментарии об измерениях и почему JAXBContext стоит дорого.
Владимир
1
Javadoc всегда был слаб в отношении важных фактов жизненного цикла . Это, безусловно, дает нам банальное повторение методов получения и установки свойств, но что касается знания того, как / где получить или создать экземпляр, мутации и безопасности потоков ... похоже, полностью упускаются эти наиболее важные факторы. Вздох :)
Thomas W
4

В JAXB 2.2 ( JSR-222 ) в разделе «4.2 JAXBContext» говорится следующее:

Чтобы избежать накладных расходов, связанных с созданием JAXBContextэкземпляра, приложению JAXB рекомендуется повторно использовать экземпляр JAXBContext . Реализация абстрактного класса JAXBContext должна быть потокобезопасной , поэтому несколько потоков в приложении могут совместно использовать один и тот же экземпляр JAXBContext.

[..]

Класс JAXBContext спроектирован так, чтобы быть неизменным и, следовательно, потокобезопасным. Учитывая объем динамической обработки, которая потенциально может иметь место при создании нового экземпляра JAXBContxt, рекомендуется, чтобы экземпляр JAXBContext совместно использовался между потоками и использовался повторно, насколько это возможно, для повышения производительности приложения.

К сожалению, спецификация не делает никаких заявлений относительно поточной безопасности Unmarshaller и Marshaller. Так что лучше предположить, что это не так.

Мартин Андерссон
источник
3

Я решил эту проблему с помощью:

  • общий потокобезопасный JAXBContext и локальные потоки un / marschallers
  • (так что теоретически экземпляров un / 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");
        }
    }
}
peeeto
источник
8
ThreadLocal представит другие тонкие проблемы без всякой пользы. Просто сохраните один JAXBContext (это дорогостоящая часть) и при необходимости создайте новый Unmarshaller.
ymajoros 03
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 мс.

tbarderas
источник
Насколько велик был XML-файл, который вы анализировали. Вы видите значительное улучшение с очень большими файлами xml?
Джон
1
на самом деле дело не в больших xml-файлах (размер мин составляет от 2-3 Кбайт до +6 Мбайт), а скорее в очень большом количестве xml-файлов (мы говорим о примерно 10 000 xml-запросов в минуту); в этом случае создание контекста только один раз, получение этих маленьких мс имеет огромное значение
tbarderas
1

Обычно я решаю подобные проблемы с помощью ThreadLocalшаблона класса. Учитывая тот факт, что вам нужен другой маршаллер для каждого класса, вы можете комбинировать его с singletonшаблоном -map.

Чтобы сэкономить вам 15 минут работы. Здесь следует моя реализация поточно-ориентированной Factory для 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;
  }
}
bvdb
источник
10
ThreadLocal представит другие тонкие проблемы без всякой пользы. Просто сохраните один JAXBContext (это дорогостоящая часть) и при необходимости создайте новый Unmarshaller.
ymajoros 03
На самом деле вам не нужны отдельные JAXBContexts, поскольку вы можете передавать несколько классов. Итак, если вы можете предсказать, какие классы будут ранжироваться, вы можете создать один общий. Кроме того, спецификация JAXB требует, чтобы они уже были потокобезопасными.
MauganRa