JAXB membuat konteks dan biaya marshaller

120

Pertanyaannya agak teoritis, berapa biaya pembuatan konteks JAXB, marshaller dan unmarshaller?

Saya telah menemukan bahwa kode saya bisa mendapatkan keuntungan dari menjaga konteks JAXB yang sama dan mungkin marshaller yang sama untuk semua operasi marshaling daripada membuat konteks dan marshaller pada setiap marshaling.

Jadi berapa biaya untuk membuat konteks JAXB dan marshaller / unmarshaller? Bolehkah membuat konteks + marshaller untuk setiap operasi marshaling atau lebih baik menghindarinya?

Vladimir
sumber

Jawaban:

244

Catatan: Saya pimpinan EclipseLink JAXB (MOXy) dan anggota kelompok ahli JAXB 2 ( JSR-222 ).

JAXBContextadalah thread safe dan sebaiknya hanya dibuat sekali dan digunakan kembali untuk menghindari biaya inisialisasi metadata beberapa kali. Marshallerdan Unmarshallertidak aman untuk thread, tetapi ringan untuk dibuat dan dapat dibuat per operasi.

bdoughan.dll
sumber
7
jawaban yang bagus. Saya yakin sekarang berdasarkan pengalaman Anda sebagai pemimpin di JAXB.
Vladimir
7
Saya percaya Anda, tetapi apakah ini dapat ditemukan di suatu tempat dalam dokumentasi?
Hurda
3
Ini didokumentasikan untuk RI: jaxb.java.net/guide/Performance_and_thread_safety.html (tapi bukan Moxy AFAIK)
Caoilte
39
Harap sebutkan ini di Javadoc. Tidaklah memuaskan jika aspek-aspek penting ini tidak terdokumentasi.
Thomas W
6
Tidak disebutkan di Javadoc bahwa JAXBContext adalah thread safe. Dan hampir setiap contoh cara menggunakan JAXB, gagal menyebutkan bahwa itu harus dibuat sekali dan dibagikan di seluruh utas. Sebagai hasilnya, saya sekarang menghapus lagi kebocoran sumber daya melolong dari sistem langsung. Grrrrrrr.
Reg Whitton
42

Idealnya, Anda harus memiliki JAXBContextinstance tunggal dan lokal dari Marshallerdan Unmarshaller.

JAXBContextInstance aman untuk thread sementara Marshallerdan Unmarshallerinstance tidak aman untuk thread dan tidak boleh dibagikan di seluruh thread.

Sahil Muthoo
sumber
Terima kasih atas jawabannya. Sayangnya saya harus memilih hanya satu jawaban :-)
Vladimir
15

Sayangnya hal ini tidak dijelaskan secara spesifik di javadoc. Apa yang dapat saya katakan adalah bahwa Spring menggunakan JAXBContext global, dibagi di antara utas, sedangkan itu menciptakan marshaller baru untuk setiap operasi marshalling, dengan komentar javadoc dalam kode yang mengatakan bahwa marshaller JAXB belum tentu aman untuk thread.

Hal yang sama dikatakan di halaman ini: https://javaee.github.io/jaxb-v2/doc/user-guide/ch03.html#other-miscellaneous-topics-performance-and-thread-safety .

Saya akan menebak bahwa membuat JAXBContext adalah operasi yang mahal, karena melibatkan pemindaian kelas dan paket untuk penjelasan. Tapi mengukurnya adalah cara terbaik untuk mengetahuinya.

JB Nizet
sumber
Hai @JB, jawaban bagus terutama komentar Anda tentang pengukuran dan mengapa JAXBContext mahal.
Vladimir
1
Javadoc selalu lemah tentang fakta-fakta penting dari siklus hidup . Ini pasti memberi kita pengulangan yang sepele dari pengambil & penyetel properti, tetapi untuk mengetahui bagaimana / di mana mendapatkan atau membuat instance, mutasi & keamanan utas .. tampaknya sepenuhnya melewatkan faktor terpenting tersebut. Sigh :)
Thomas W
4

JAXB 2.2 ( JSR-222 ) mengatakan ini, di bagian "4.2 JAXBContext":

Untuk menghindari overhead yang terlibat dalam pembuatan JAXBContextinstance, aplikasi JAXB didorong untuk menggunakan kembali instance JAXBContext . Implementasi kelas abstrak JAXBContext diperlukan agar thread-safe , sehingga, beberapa thread dalam aplikasi dapat berbagi instance JAXBContext yang sama.

[..]

Kelas JAXBContext dirancang agar tidak berubah dan karenanya aman untuk thread. Mengingat jumlah pemrosesan dinamis yang berpotensi terjadi saat membuat instance baru JAXBContxt, disarankan agar instance JAXBContext dibagikan di seluruh utas dan digunakan kembali sebanyak mungkin untuk meningkatkan kinerja aplikasi.

Sayangnya, spesifikasi tersebut tidak membuat klaim apa pun terkait keamanan ulir Unmarshallerdan Marshaller. Jadi yang terbaik adalah menganggap mereka tidak benar.

Martin Andersson
sumber
3

Saya memecahkan masalah ini menggunakan:

  • berbagi benang JAXBConteks aman dan benang un / marschallers lokal
  • (jadi secara teoritis, akan ada banyak instance un / marshaller karena ada thread yang mengaksesnya)
  • dengan sinkronisasi hanya pada inisialisasi 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
sumber
8
ThreadLocal akan memperkenalkan masalah subtil lainnya, tanpa manfaat. Cukup simpan satu JAXBContext (itu bagian yang mahal) dan buat Unmarshaller baru kapan pun diperlukan.
ymajoros
2

Bahkan lebih baik!! Berdasarkan solusi yang baik dari posting di atas, buat konteks hanya sekali dalam konstruktor, dan simpan sebagai ganti kelas.

Ganti baris:

  private Class clazz;

dengan yang ini:

  private JAXBContext jc;

Dan konstruktor utama dengan yang ini:

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

jadi di getMarshaller / getUnmarshaller Anda dapat menghapus baris ini:

  JAXBContext jc = JAXBContext.newInstance(clazz);

Peningkatan ini membuat, dalam kasus saya, waktu pemrosesan turun dari 60 ~ 70ms menjadi hanya 5 ~ 10ms

tbarderas
sumber
Seberapa besar file xml yang Anda parse. Apakah Anda melihat peningkatan yang signifikan dengan file xml yang sangat besar?
Yohanes
1
ini sebenarnya bukan masalah file xml yang besar (ranjau berjalan dari hanya 2-3kb hingga + 6mb), melainkan masalah sejumlah besar file xml (kita berbicara di sini tentang sekitar 10.000 permintaan xml per menit); dalam hal ini membuat konteks hanya sekali mendapatkan ms kecil itu membuat perbedaan besar
tbarderas
1

Saya biasanya memecahkan masalah seperti ini dengan ThreadLocalpola kelas. Mengingat fakta bahwa Anda memerlukan marshaller yang berbeda untuk setiap Kelas, Anda dapat menggabungkannya dengan singletonpola -map.

Untuk menghemat waktu kerja 15 menit. Di sini mengikuti implementasi saya tentang Pabrik thread-safe untuk Jaxb Marshallers dan Unmarshallers.

Ini memungkinkan Anda untuk mengakses instance sebagai berikut ...

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

Dan kode yang Anda perlukan adalah kelas Jaxb kecil yang terlihat seperti berikut:

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.dll
sumber
10
ThreadLocal akan memperkenalkan masalah subtil lainnya, tanpa manfaat. Cukup simpan satu JAXBContext (itu bagian yang mahal) dan buat Unmarshaller baru kapan pun diperlukan.
ymajoros
Anda sebenarnya tidak memerlukan JAXBContexts terpisah karena Anda dapat lulus di banyak kelas. Jadi, jika Anda dapat memprediksi kelas mana yang akan diatur, Anda dapat membuat satu kelas bersama. Selain itu, spesifikasi JAXB mengharuskan mereka untuk menjadi threadsafe.
MauganRa