1. Kelas singleton gaya lapar
}
instance Singleton statis pribadi = new Singleton();
getInstance Singleton statis pribadi(){
contoh kembali;
}
}
Fitur: Instansiasi gaya lapar terlebih dahulu, tidak ada masalah multi-threading dalam gaya malas, tetapi tidak peduli apakah kita memanggil getInstance() atau tidak, akan ada instance di memori
2. Kelas tunggal kelas internal
}
kelas pribadi SingletonHoledr(){
instance Singleton statis pribadi = new Singleton();
}
getInstance Singleton statis pribadi(){
kembalikan SingletonHoledr.instance;
}
}
Fitur: Di kelas internal, pemuatan lambat diimplementasikan. Hanya ketika kita memanggil getInstance() instance unik akan dibuat di memori. Ini juga memecahkan masalah multi-threading dalam gaya malas .
3. Kelas Lajang Malas
}
contoh Singleton statis pribadi;
getInstance Singleton statis publik(){
jika(contoh == nol){
contoh kembali = new Singleton();
}kalau tidak{
contoh kembali;
}
}
}
Fitur: Dalam gaya malas, ada thread A dan B. Ketika thread A berjalan ke baris 8, ia melompat ke thread B. Ketika B juga berjalan ke baris 8, instance dari kedua thread kosong, sehingga akan menghasilkan Dua contoh . Solusinya adalah dengan melakukan sinkronisasi:
Sinkronisasi dimungkinkan tetapi tidak efisien:
}
contoh Singleton statis pribadi;
Singleton getInstance tersinkronisasi statis publik(){
jika(contoh == nol){
contoh kembali = new Singleton();
}kalau tidak{
contoh kembali;
}
}
}
Tidak akan ada kesalahan dalam penulisan program seperti ini, karena keseluruhan getInstance adalah "bagian kritis" keseluruhan, tetapi efisiensinya sangat buruk, karena tujuan kita sebenarnya hanya untuk mengunci saat menginisialisasi instance untuk pertama kalinya, dan kemudian dapatkan Saat menggunakan instance, tidak diperlukan sinkronisasi thread sama sekali.
Jadi orang pintar datang dengan pendekatan berikut:
Periksa kembali metode penulisan kunci:
public static Singleton getSingle(){ //Objek eksternal dapat diperoleh melalui metode ini
jika(tunggal == nol){
disinkronkan (Singleton.class) {//Ini memastikan bahwa hanya satu objek yang dapat mengakses blok tersinkronisasi ini pada saat yang sama
jika(tunggal == nol){
tunggal = singleton baru();
}
}
}
return single; //Mengembalikan objek yang dibuat
}
}
Idenya sangat sederhana, yaitu kita hanya perlu menyinkronkan (synchronize) bagian kode yang menginisialisasi instance agar kode tersebut benar dan efisien.
Inilah yang disebut mekanisme "kunci periksa ganda" (seperti namanya).
Sayangnya, cara penulisan ini salah di banyak platform dan kompiler pengoptimalan.
Alasannya adalah perilaku baris kode instance = new Singleton() pada kompiler yang berbeda tidak dapat diprediksi. Kompiler pengoptimal dapat mengimplementasikan instance = new Singleton() secara legal sebagai berikut:
1. instance = mengalokasikan memori ke entitas baru
2. Panggil konstruktor Singleton untuk menginisialisasi variabel anggota instance
Sekarang bayangkan thread A dan B memanggil getInstance. Thread A masuk terlebih dahulu dan dikeluarkan dari CPU ketika langkah 1 dijalankan. Kemudian thread B masuk, dan yang dilihat B adalah instance tersebut tidak lagi null (memori telah dialokasikan), sehingga mulai menggunakan instance dengan percaya diri, tetapi ini salah, karena saat ini variabel anggota instance masih default. nilai, A belum punya waktu untuk menjalankan langkah 2 untuk menyelesaikan inisialisasi instance.
Tentu saja kompiler juga dapat mengimplementasikannya seperti ini:
1. temp = mengalokasikan memori
2. Panggil konstruktor temp
3. contoh = suhu
Jika kompiler berperilaku seperti ini, sepertinya kita tidak memiliki masalah, tetapi kenyataannya tidak sesederhana itu, karena kita tidak dapat mengetahui bagaimana kompiler tertentu melakukannya, karena masalah ini tidak didefinisikan dalam model memori Java.
Penguncian periksa ganda berlaku untuk tipe dasar (seperti int). Jelas karena tipe dasarnya tidak memanggil konstruktor.