Koszt tworzenia kontekstu i organizatorów JAXB

120

Pytanie jest trochę teoretyczne, jaki jest koszt stworzenia kontekstu JAXB, marshallera i unmarshaller?

Zauważyłem, że mój kod mógłby skorzystać na zachowaniu tego samego kontekstu JAXB i prawdopodobnie tego samego organizatora dla wszystkich operacji kierowania, zamiast tworzenia kontekstu i organizatora na każdym organizowaniu.

Więc jaki jest koszt stworzenia kontekstu JAXB i Marshaller / Unmarshaller? Czy można utworzyć kontekst + organizator dla każdej operacji kierowania, czy lepiej tego uniknąć?

Vladimir
źródło

Odpowiedzi:

244

Uwaga: jestem liderem EclipseLink JAXB (MOXy) i członkiem grupy ekspertów JAXB 2 ( JSR-222 ).

JAXBContextjest bezpieczny dla wątków i powinien być tworzony tylko raz i ponownie używany, aby uniknąć kosztów wielokrotnej inicjalizacji metadanych. Marshalleri Unmarshallernie są bezpieczne dla wątków, ale są lekkie w tworzeniu i można je utworzyć za pomocą operacji.

bdoughan
źródło
7
świetna odpowiedź. Teraz mogę być pewny, opierając się na twoim doświadczeniu jako lidera w JAXB.
Vladimir,
7
Ufam ci, ale czy można to znaleźć gdzieś w dokumentacji?
Hurda
3
Jest to udokumentowane dla RI: jaxb.java.net/guide/Performance_and_thread_safety.html (ale nie Moxy AFAIK)
Caoilte
39
Proszę określić to w Javadoc. Brak udokumentowania tych kluczowych aspektów nie jest satysfakcjonujący.
Thomas W.
6
W Javadoc nie wspomniano, że JAXBContext jest bezpieczny wątkowo. I prawie każdy przykład użycia JAXB nie wspomina, że ​​powinien zostać utworzony raz i udostępniony w różnych wątkach. W rezultacie usuwam kolejny wyciek zasobów z działającego systemu. Grrrrrrr.
Reg Whitton,
42

Najlepiej byłoby, gdybyś miał pojedyncze JAXBContexti lokalne wystąpienia MarshalleriUnmarshaller .

JAXBContextwystąpienia są bezpieczne dla wątków, Marshallera Unmarshallerwystąpienia nie są bezpieczne dla wątków i nigdy nie powinny być współużytkowane w wątkach.

Sahil Muthoo
źródło
Dziękuję za odpowiedź. Niestety muszę zaznaczyć tylko jedną odpowiedź :-)
Vladimir
15

Szkoda, że ​​nie jest to szczegółowo opisane w javadoc. Mogę powiedzieć, że Spring używa globalnego JAXBContext, współdzielonego między wątkami, podczas gdy tworzy nowy organizator dla każdej operacji kierowania z komentarzem javadoc w kodzie mówiącym, że organizatory JAXB niekoniecznie są bezpieczne dla wątków.

To samo jest powiedziane na tej stronie: https://javaee.github.io/jaxb-v2/doc/user-guide/ch03.html#other-miscellaneous-topics-performance-and-thread-safety .

Domyślam się, że tworzenie JAXBContext jest kosztowną operacją, ponieważ wiąże się ze skanowaniem klas i pakietów pod kątem adnotacji. Jednak mierzenie tego jest najlepszym sposobem, aby to wiedzieć.

JB Nizet
źródło
Cześć @JB, świetna odpowiedź, szczególnie twoje komentarze na temat pomiaru i dlaczego JAXBContext jest kosztowne.
Vladimir,
1
Javadoc zawsze był słaby w kluczowych faktach dotyczących cyklu życia . Z pewnością daje nam to trywialne powtórzenie metod pobierających i ustawiających właściwości, ale jeśli chodzi o wiedzę, jak / gdzie uzyskać lub utworzyć instancję, mutację i bezpieczeństwo wątków ... wydaje się, że całkowicie pomija te najważniejsze czynniki. Westchnienie :)
Thomas W
4

JAXB 2.2 ( JSR-222 ) ma to do powiedzenia w sekcji „4.2 JAXBContext”:

Aby uniknąć narzutu związanego z tworzeniem JAXBContextinstancji, zaleca się, aby aplikacja JAXB ponownie używała instancji JAXBContext . Implementacja klasy abstrakcyjnej JAXBContext musi być bezpieczna dla wątków , dlatego wiele wątków w aplikacji może współużytkować tę samą instancję JAXBContext.

[..]

Klasa JAXBContext została zaprojektowana jako niezmienna, a tym samym bezpieczna wątkowo. Biorąc pod uwagę ilość dynamicznego przetwarzania, które potencjalnie mogłoby mieć miejsce podczas tworzenia nowej instancji JAXBContxt, zaleca się współużytkowanie instancji JAXBContext przez wątki i ponowne wykorzystanie w jak największym stopniu w celu poprawy wydajności aplikacji.

Niestety specyfikacja nie zawiera żadnych zastrzeżeń dotyczących bezpieczeństwa wątków Unmarshalleri Marshaller. Dlatego najlepiej założyć, że tak nie jest.

Martin Andersson
źródło
3

Rozwiązałem ten problem używając:

  • współdzielony wątek bezpieczny JAXBContext i lokalny wątek un / marschallers
  • (więc teoretycznie będzie tyle wystąpień un / marshaller, ile jest wątków, które uzyskały do ​​nich dostęp)
  • z synchronizacją tylko przy inicjalizacji 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
źródło
8
ThreadLocal wprowadzi inne subtelne problemy bez korzyści. Po prostu zachowaj jeden JAXBContext (to kosztowna część) i utwórz nowy Unmarshaller, gdy zajdzie taka potrzeba.
ymajoros
2

Nawet lepiej!! Bazując na dobrym rozwiązaniu z powyższego postu, stwórz kontekst tylko raz w konstruktorze i zapisz go zamiast klasy.

Wymień linię:

  private Class clazz;

z tym:

  private JAXBContext jc;

A główny konstruktor z tym:

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

więc w getMarshaller / getUnmarshaller możesz usunąć tę linię:

  JAXBContext jc = JAXBContext.newInstance(clazz);

To ulepszenie sprawia, że ​​w moim przypadku czas przetwarzania spada z 60 ~ 70 ms do zaledwie 5 ~ 10 ms

tbarderas
źródło
Jak duży był analizowany plik xml. Czy widzisz znaczną poprawę w przypadku bardzo dużych plików xml?
John
1
tak naprawdę nie jest to kwestia dużych plików xml (kopalnie mają od zaledwie 2-3kb do + 6mb), ale raczej kwestia bardzo dużej liczby plików xml (mówimy tutaj o około 10 000 żądań xml na minutę); w takim razie stworzenie kontekstu tylko raz zdobycie tych małych ms robi ogromną różnicę
tbarderas
1

Zazwyczaj rozwiązuję takie problemy za pomocą ThreadLocalwzorca klasowego. Biorąc pod uwagę fakt, że potrzebujesz innego koordynatora dla każdej klasy, możesz połączyć go ze singletonwzorem -mapy.

Aby zaoszczędzić 15 minut pracy. Oto moja implementacja bezpiecznej wątkowo fabryki dla Jaxb Marshallers i Unmarshallers.

Umożliwia dostęp do instancji w następujący sposób ...

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

Kod, którego będziesz potrzebować, to mała klasa Jaxb, która wygląda następująco:

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
źródło
10
ThreadLocal wprowadzi inne subtelne problemy bez korzyści. Po prostu zachowaj jeden JAXBContext (to kosztowna część) i utwórz nowy Unmarshaller, gdy zajdzie taka potrzeba.
ymajoros
Właściwie nie potrzebujesz oddzielnych JAXBContexts, ponieważ możesz przekazać wiele klas. Tak więc, jeśli potrafisz przewidzieć, które klasy zostaną zorganizowane, możesz utworzyć jedną wspólną. Ponadto specyfikacja JAXB wymaga, aby były one już bezpieczne dla wątków.
MauganRa