Semua orang akrab dengan model Singleton, dan mereka semua tahu mengapa malas, lapar, dll. Tetapi apakah Anda memiliki pemahaman menyeluruh tentang pola singleton? Hari ini saya akan membawa Anda untuk melihat singleton di mata saya, yang mungkin berbeda dari pemahaman Anda.
Berikut adalah contoh kecil sederhana:
// singleton kelas publik malas sederhana {// variabel singleton variabel private static singleton instance = null; // Metode konstruksi pribadi untuk memastikan bahwa kelas eksternal tidak dapat dipakai melalui konstruktor singleton pribadi () {} // Dapatkan singleton instance public static singleton getInstance () {if (instance == null) {instance = singleton baru (); } System.out.println ("Saya singleton malas sederhana!"); instance return; }}Mudah untuk melihat bahwa kode di atas tidak aman dalam kasus multi-threading. Ketika dua utas masuk if (instance == null), kedua utas menilai bahwa instance kosong, dan kemudian dua instance akan diperoleh. Ini bukan singleton yang kita inginkan.
Selanjutnya, kami menggunakan penguncian untuk mencapai pengecualian timbal balik untuk memastikan implementasi singleton.
// metode sinkron malas kelas publik singleton {// variabel singleton variabel private static singleton instance = null; // Metode Konstruksi Pribadi Untuk memastikan bahwa kelas eksternal tidak dapat dipakai melalui konstruktor singleton pribadi () {} // Dapatkan singleton instance public static static singleton getInstance () {if (instance == null) {instance = singleton baru (); } System.out.println ("Saya adalah metode sinkron malas singleton!"); instance return; }}Menambahkan sinkronisasi apakah memastikan keamanan utas, tetapi apakah ini cara terbaik? Jelas tidak, karena dengan cara ini, setiap kali kita menelepon metode getInstance (), kita akan dikunci, dan kita hanya perlu menguncinya ketika kita memanggil getInstance () pertama kali. Ini jelas mempengaruhi kinerja program kami. Kami terus menemukan cara yang lebih baik.
Setelah analisis, ditemukan bahwa hanya dengan memastikan bahwa instance = singleton baru () adalah utas pengecualian, keamanan utas dapat dipastikan, sehingga versi berikut tersedia:
// Double Lock Lazy Public Class Singleton {// variabel singleton variabel private static singleton instance = null; // Metode Konstruksi Pribadi Untuk memastikan bahwa kelas eksternal tidak dapat dipakai melalui konstruktor singleton pribadi () {} // dapatkan singleton instance public static singleton getInstance () {if (instance == null) {sinkronisasi (singleton.class) {if (instance == null) {instance = singleton baru (); }}} System.out.println ("Saya adalah singleton malas kunci ganda!"); instance return; }} Kali ini tampaknya tidak hanya memecahkan masalah keamanan utas, tetapi tidak menyebabkan penguncian ditambahkan setiap kali Anda menelepon getInstance (), menghasilkan degradasi kinerja. Sepertinya solusi yang sempurna, apakah sebenarnya seperti itu?
Sayangnya, faktanya tidak sesempurna yang kita kira. Ada mekanisme yang disebut "out-of-order menulis" dalam model memori platform Java. Mekanisme inilah yang menyebabkan kegagalan metode penguncian pemeriksaan ganda. Kunci masalah ini terletak pada baris 5 pada kode di atas: instance = new singleton (); Baris ini sebenarnya melakukan dua hal: 1. Panggil konstruktor dan buat instance. 2. Tetapkan instance ini ke instance variabel instance. Tetapi masalahnya adalah bahwa dua langkah JVM ini tidak menjamin pesanan. Artinya. Mungkin instance telah diatur ke non-kosong sebelum memanggil konstruktor. Mari kita analisis bersama:
Misalkan ada dua utas A dan B
1. Thread A memasuki metode getInstance ().
2. Karena instance kosong saat ini, utas A memasuki blok yang disinkronkan.
3. Thread A mengeksekusi instance = singleton baru (); Menetapkan instance variabel instance ke non-kosong. (Perhatikan bahwa itu sebelum memanggil konstruktor.)
4. Thread A Exit dan Thread B masuk.
5. Thread B memeriksa apakah instance kosong, dan tidak kosong saat ini (pada langkah ketiga, diatur ke non-kosong oleh utas A). Thread B mengembalikan referensi ke Instance. (Masalahnya muncul. Saat ini, referensi ke contoh bukanlah contoh singleton karena konstruktor tidak dipanggil.)
6. Thread B Keluar dan Thread A masuk.
7. Thread A terus memanggil metode konstruktor, melengkapi inisialisasi instance, dan kembali.
Bukankah ada cara yang baik? Pasti ada cara yang baik, mari kita terus jelajahi!
// Selesaikan masalah penulisan kelas publik malas singleton {// variabel singleton variabel private static singleton instance = null; // Metode konstruksi pribadi untuk memastikan bahwa kelas eksternal tidak dapat dipakai melalui konstruktor singleton pribadi () {} // Dapatkan contoh singleton singleton public staticon getInstance () {if (instance == null) {disinkronkan (singleton.class) {// 1 singleton temp = instance; // 2 if (temp == null) {disinkronkan (singleton.class) {// 3 temp = singleton baru (); // 4} instance = temp; // 5}}} System.out.println ("Saya memecahkan penulisan singleton malas yang tidak dipesan!"); instance return; }} 1. Thread A memasuki metode getInstance ().
2. Karena instance kosong, utas A memasuki blok yang disinkronkan pertama di posisi // 1.
3. Thread A mengeksekusi kode pada posisi // 2 dan memberikan instance ke suhu variabel lokal. Contoh kosong, jadi temp juga kosong.
4. Karena suhu kosong, utas A memasuki blok tersinkronisasi kedua pada posisi // 3. (Saya pikir kunci ini agak berlebihan)
5. Thread A mengeksekusi kode pada posisi // 4, mengatur suhu ke non-kosong, tetapi konstruktor belum dipanggil! (Masalah "Unorder Writing")
6. Jika utas blok, utas B memasuki metode getInstance ().
7. Karena instance kosong, Thread B mencoba memasukkan blok yang disinkronkan pertama. Tetapi karena Thread A sudah ada di dalam. Jadi tidak mungkin masuk. Blok Thread B.
8. Thread A diaktifkan dan terus menjalankan kode di posisi // 4. Hubungi konstruktor. Menghasilkan contoh.
9. Tetapkan referensi instance dari temp Keluar dari dua blok yang disinkronkan. Mengembalikan instance.
10. Thread B diaktifkan dan memasuki blok yang disinkronkan pertama.
11. Thread B menjalankan kode pada posisi // 2 dan menetapkan instance instance ke variabel lokal temp.
12. Thread B menentukan bahwa suhu variabel lokal tidak kosong, jadi lewati blok IF. Mengembalikan instance instance.
Sejauh ini, kami telah memecahkan masalah di atas, tetapi kami tiba -tiba menemukan bahwa untuk menyelesaikan masalah keamanan utas, rasanya ada banyak benang yang melilit tubuh ... itu berantakan, jadi kami perlu merampingkannya:
// Singleton kelas publik yang lapar {// variabel singleton, statis, diinisialisasi sekali ketika kelas dimuat untuk memastikan keselamatan utas singleton singleton private instance = singleton baru (); // Metode konstruksi pribadi untuk memastikan bahwa kelas eksternal tidak dapat dipakai melalui konstruktor. private singleton () {} // Dapatkan instance objek singleton public static singleton getInstance () {System.out.println ("I Am A Hungry Singleton!"); instance return; }}Ketika saya melihat kode di atas, saya langsung merasa bahwa dunia diam. Namun, metode ini mengadopsi metode gaya manusia yang lapar, yaitu untuk objek singleton pra-deklare. Salah satu kelemahannya adalah: jika singleton konstruksi besar dan tidak digunakan setelah konstruksi selesai, itu akan menyebabkan pemborosan sumber daya.
Apakah ada cara yang sempurna? Terus tonton:
// kelas dalam mengimplementasikan malas kelas publik singleton {private static class singletonHolder {// variabel singleton private static singleton instance = new singleton (); } // Metode konstruksi pribadi untuk memastikan bahwa kelas eksternal tidak dapat dipakai melalui konstruktor. private singleton () {} // dapatkan singleton objek instance public static singleton getInstance () {System.out.println ("I Am An Internal Class Singleton!"); mengembalikan singletonHolder.instance; }}Lazy (hindari limbah sumber daya di atas), aman, dan kode sederhana. Karena mekanisme Java menetapkan bahwa singletonHolder kelas internal hanya akan dimuat ketika metode getInstance () dipanggil untuk pertama kalinya (mengimplementasikan malas), dan proses pemuatannya aman-aman (mengimplementasikan thread-aman). Contoh dipakai saat kelas internal dimuat.
Mari kita bicara secara singkat tentang tulisan yang tidak tertib yang disebutkan di atas. Ini adalah karakteristik JVM. Misalnya, menyatakan dua variabel, string A; String b; JVM dapat memuat pertama atau b. Demikian pula, instance = singleton baru (); dapat menetapkan instance ke non-kosong sebelum memanggil konstruktor Singleton. Ini adalah pertanyaan bagi banyak orang, mengatakan bahwa objek Singleton belum dipakai, jadi bagaimana contoh menjadi tidak kosong? Berapa nilainya sekarang? Jika Anda ingin memahami masalah ini, Anda harus memahami bagaimana contoh kalimat = singleton baru (); dieksekusi. Berikut adalah kode pseudo untuk menjelaskannya kepada Anda:
mem = alokasikan (); // Alokasikan memori untuk objek singleton. instance = mem; // Perhatikan bahwa instance ini tidak kosong sekarang, tetapi belum diinisialisasi. ctorsingleton (instance); // Hubungi konstruktor singleton dan lulus instance.
Dapat dilihat bahwa ketika utas mengeksekusi instance = mem;, instance tidak kosong. Jika utas lain memasuki program dan juri sebagai tidak kosong, ia akan melompat untuk mengembalikan instance; Dan pada saat ini, konstruktor Singleton belum memanggil instance, dan nilai saat ini adalah objek memori yang dikembalikan oleh allocate ();. Jadi utas kedua tidak mendapatkan objek singleton, tetapi objek memori.
Di atas adalah pikiran kecil saya dan pemahaman tentang model singleton. Saya dengan hangat menyambut semua dewa agung untuk datang dan membimbing dan mengkritik.