Apakah thread penginisialisasi statis Java aman?

138

Saya menggunakan blok kode statis untuk menginisialisasi beberapa pengontrol di registri yang saya miliki. Oleh karena itu, pertanyaan saya adalah, dapatkah saya menjamin bahwa blok kode statis ini hanya akan benar-benar dipanggil sekali ketika kelas pertama kali dimuat? Saya mengerti bahwa saya tidak dapat menjamin kapan blok kode ini akan dipanggil, saya menduga ketika Classloader memuatnya pertama kali. Saya menyadari bahwa saya dapat melakukan sinkronisasi di kelas dalam blok kode statis, tetapi tebakan saya apakah ini sebenarnya yang terjadi?

Contoh kode sederhana adalah;

class FooRegistry {

    static {
        //this code must only ever be called once 
        addController(new FooControllerImpl());
    }

    private static void addController(IFooController controller) { 
        // ...
    }
}

atau haruskah saya melakukan ini;

class FooRegistry {

    static {
        synchronized(FooRegistry.class) {
            addController(new FooControllerImpl());
        }
    }

    private static void addController(IFooController controller) {
        // ...
    }
}
simon622
sumber
10
Saya tidak suka desain ini, karena tidak dapat diuji. Lihat Injeksi Ketergantungan.
dfa

Jawaban:

201

Ya, penginisialisasi statis Java adalah thread safe (gunakan opsi pertama Anda).

Namun, jika Anda ingin memastikan bahwa kode dijalankan tepat setelah Anda perlu memastikan bahwa kelas tersebut hanya dimuat oleh satu class-loader. Inisialisasi statis dilakukan sekali per pemuat kelas.

Matthew Murdoch
sumber
2
Namun, kelas dapat dimuat oleh beberapa pemuat kelas sehingga addController masih dapat dipanggil lebih dari sekali (terlepas dari apakah Anda menyinkronkan panggilan atau tidak) ...
Matthew Murdoch
4
Ah tunggu dulu, jadi kami mengatakan bahwa blok kode statis benar-benar dipanggil untuk setiap classloader yang memuat kelas.? Hmm ... Saya kira ini masih baik-baik saja, namun, saya bertanya-tanya bagaimana menjalankan kode semacam ini di OSGI env akan bekerja, dengan mulitple bundle classloader ..
simon622
1
Iya. Blok kode statis dipanggil untuk setiap pemuat kelas yang memuat kelas.
Matthew Murdoch
3
@ simon622 Ya, tetapi itu akan beroperasi di objek kelas yang berbeda di setiap ClassLoader. Objek Kelas berbeda yang masih memiliki nama yang sepenuhnya memenuhi syarat yang sama, tetapi mewakili tipe berbeda yang tidak dapat ditransmisikan satu sama lain.
Erwin Bolwidt
1
apakah ini berarti bahwa kata kunci 'final' berlebihan dalam pemegang contoh di: en.wikipedia.org/wiki/Initialization-on-demand_holder_idiom ?
spc16670
11

Ini adalah trik yang dapat Anda gunakan untuk inisialisasi lambat

enum Singleton {
    INSTANCE;
}

atau untuk pra Java 5.0

class Singleton {
   static class SingletonHolder {
      static final Singleton INSTANCE = new Singleton();
   }
   public static Singleton instance() {
      return SingletonHolder.INSTANCE;
   }
}

Karena blok statis di SingletonHolder akan berjalan satu kali dengan cara yang aman, Anda tidak memerlukan penguncian lainnya. Kelas SingletonHolder hanya akan dimuat ketika Anda memanggil instance ()

Peter Lawrey
sumber
18
Anda mendasarkan jawaban ini pada fakta bahwa blok statis hanya akan dijalankan sekali secara global - yang merupakan pertanyaan yang ditanyakan.
Michael Myers
2
Saya pikir ini juga tidak aman di lingkungan loader multi-kelas apa yang dikatakan.?
Ahmad
2
@Ahmad Lingkungan pemuat multi-kelas dirancang untuk memungkinkan setiap aplikasi memiliki lajang sendiri.
Peter Lawrey
1
Kelas bertingkat tidak diperlukan. Konstruksi ini bekerja dengan cara yang sama ketika INSTANCEbidang telah dideklarasikan secara langsung Singleton(seperti kasus enumvarian).
Holger
4

Dalam keadaan biasa, semua yang ada di penginisialisasi statis terjadi sebelum semua yang menggunakan kelas itu, jadi sinkronisasi biasanya tidak diperlukan. Namun, kelas dapat diakses oleh semua yang dipanggil oleh intiailiser statis (termasuk menyebabkan inisialisasi statis lainnya dipanggil).

Kelas dapat dimuat oleh kelas yang dimuat tetapi tidak harus langsung dijalankan. Tentu saja, kelas dapat dimuat oleh beberapa contoh pemuat kelas dan dengan demikian menjadi beberapa kelas dengan nama yang sama.

Tom Hawtin - tackline
sumber
3

Ya, semacam itu

Sebuah staticinitializer hanya dipanggil sekali, sehingga dengan itu definisi itu thread aman - Anda akan perlu dua atau lebih doa dari staticinitializer bahkan mendapatkan benang pertengkaran.

Yang mengatakan, staticpenginisialisasi membingungkan dalam banyak hal lain. Sebenarnya tidak ada urutan tertentu untuk memanggilnya. Ini menjadi sangat membingungkan jika Anda memiliki dua kelas yang staticpenginisialisasinya bergantung satu sama lain. Dan jika Anda menggunakan kelas tetapi tidak menggunakan apa yang staticakan disiapkan penginisialisasi, Anda tidak dijamin pemuat kelas akan memanggil penginisialisasi statis.

Terakhir, ingatlah objek yang Anda sinkronkan. Saya menyadari ini bukan yang Anda tanyakan, tetapi pastikan pertanyaan Anda tidak benar-benar menanyakan apakah Anda perlu membuat addController()thread-safe.

Matt
sumber
5
Ada urutan yang sangat jelas di mana mereka dipanggil: Berdasarkan urutan dalam kode sumber.
mafu
Juga, mereka selalu dipanggil, tidak peduli jika Anda menggunakan hasilnya. Kecuali jika ini diubah di Jawa 6.
mafu
8
Di dalam kelas, penginisialisasi mengikuti kode. Diberikan dua atau lebih kelas, itu tidak didefinisikan sebagai kelas mana yang diinisialisasi lebih dulu, apakah satu kelas diinisialisasi 100% sebelum yang lain dimulai, atau bagaimana hal-hal "disisipkan". Misalnya jika dua kelas masing-masing memiliki initalizer statis yang merujuk satu sama lain, semuanya akan menjadi buruk dengan cepat. Saya pikir ada cara Anda bisa merujuk ke final int statis ke kelas lain tanpa meminta penginisialisasi tetapi saya tidak akan memperdebatkan poin dengan satu atau lain cara
Matt
Itu memang jelek, dan saya akan menghindarinya. Tetapi ada cara yang ditentukan untuk bagaimana siklus diselesaikan. Mengutip "The Java Programming Language 4th Edition": Halaman: 75, Bagian: 2.5.3. Inisialisasi Statis: "Jika siklus terjadi, penginisialisasi statis X akan dijalankan hanya ke titik di mana metode Y dipanggil. Ketika Y, pada gilirannya, memanggil metode X, metode tersebut berjalan dengan penginisialisasi statis lainnya yang belum dieksekusi "
JMI MADISON
0

Ya, penginisialisasi statis dijalankan hanya sekali. Baca ini untuk informasi lebih lanjut .

Mike Pone
sumber
2
Tidak, mereka dapat dijalankan lebih dari sekali.
Penebusan Terbatas
5
Tidak, mereka dapat dijalankan sekali PER CLASSLOADER.
ruurd
Jawaban dasar: Init statis hanya berjalan sekali. Jawaban lanjutan: Init statis berjalan satu kali per pemuat kelas. Komentar pertama membingungkan karena frasa mencampurkan kedua jawaban ini.
JMI MADISON
-4

Jadi pada dasarnya, karena Anda menginginkan instance tunggal, Anda harus melakukannya kurang lebih dengan cara lama dan memastikan objek tunggal Anda diinisialisasi sekali dan hanya sekali.

ruurd
sumber