1. คลาสซิงเกิลตันสไตล์หิว
-
อินสแตนซ์ Singleton แบบคงที่ส่วนตัว = Singleton ใหม่ ();
ซิงเกิลตันคงที่ส่วนตัว getInstance () {
ส่งคืนอินสแตนซ์;
-
-
คุณสมบัติ: การสร้างอินสแตนซ์สไตล์หิวล่วงหน้า ไม่มีปัญหาแบบมัลติเธรดในรูปแบบขี้เกียจ แต่ไม่ว่าเราจะเรียก getInstance() หรือไม่ก็ตาม ก็จะมีอินสแตนซ์อยู่ในหน่วยความจำ
2. คลาสซิงเกิลตันภายใน
-
คลาสส่วนตัว SingletonHoledr(){
อินสแตนซ์ Singleton แบบคงที่ส่วนตัว = Singleton ใหม่ ();
-
ซิงเกิลตันคงที่ส่วนตัว getInstance () {
กลับ SingletonHoledr.instance;
-
-
คุณสมบัติ: ในคลาสภายใน การโหลดแบบ Lazy Loading จะถูกนำมาใช้เฉพาะเมื่อเราเรียก getInstance() เท่านั้นที่จะถูกสร้างขึ้นในหน่วยความจำ นอกจากนี้ยังช่วยแก้ปัญหาของมัลติเธรดในรูปแบบ Lazy อีกด้วย วิธีแก้ไขคือการใช้คุณลักษณะ Classloader .
3. คลาสขี้เกียจซิงเกิลตัน
-
อินสแตนซ์ Singleton แบบคงที่ส่วนตัว
สาธารณะ singleton getInstance () {
ถ้า (อินสแตนซ์ == null) {
ส่งคืนอินสแตนซ์ = ซิงเกิลตันใหม่ ();
}อื่น{
ส่งคืนอินสแตนซ์;
-
-
-
คุณสมบัติ: ในรูปแบบ Lazy มีเธรด A และ B เมื่อเธรด A วิ่งไปที่บรรทัดที่ 8 มันจะข้ามไปที่เธรด B เมื่อ B วิ่งไปที่บรรทัดที่ 8 ด้วย อินสแตนซ์ของทั้งสองเธรดจะว่างเปล่า ดังนั้นจะสร้างสองตัวอย่าง . วิธีแก้ไขคือการซิงโครไนซ์:
การซิงโครไนซ์สามารถทำได้แต่ไม่มีประสิทธิภาพ:
-
อินสแตนซ์ Singleton แบบคงที่ส่วนตัว
สาธารณะแบบซิงโครไนซ์ Singleton getInstance () {
ถ้า (อินสแตนซ์ == null){
ส่งคืนอินสแตนซ์ = ซิงเกิลตันใหม่ ();
}อื่น{
ส่งคืนอินสแตนซ์;
-
-
-
จะไม่มีข้อผิดพลาดในการเขียนโปรแกรมเช่นนี้ เนื่องจาก getInstance ทั้งหมดเป็น "ส่วนสำคัญ" ทั้งหมด แต่ประสิทธิภาพต่ำมาก เพราะจริงๆ แล้วจุดประสงค์ของเราคือการล็อคเมื่อเริ่มต้นอินสแตนซ์เป็นครั้งแรกเท่านั้น จากนั้น รับอินสแตนซ์เมื่อใช้ ไม่จำเป็นต้องซิงโครไนซ์เธรดเลย
คนฉลาดจึงเกิดแนวทางดังต่อไปนี้:
วิธีการเขียนเช็คล็อคสองครั้ง:
public static Singleton getSingle(){ //สามารถรับออบเจ็กต์ภายนอกได้ด้วยวิธีนี้
ถ้า (เดี่ยว == โมฆะ) {
ซิงโครไนซ์ (Singleton.class) { // ทำให้แน่ใจว่ามีเพียงวัตถุเดียวเท่านั้นที่สามารถเข้าถึงบล็อกที่ซิงโครไนซ์นี้ในเวลาเดียวกัน
ถ้า (เดี่ยว == โมฆะ) {
single = ซิงเกิลตันใหม่ ();
-
-
-
return single; // ส่งคืนวัตถุที่สร้างขึ้น
-
-
แนวคิดนี้ง่ายมาก กล่าวคือ เราเพียงต้องซิงโครไนซ์ (ซิงโครไนซ์) ส่วนของโค้ดที่เริ่มต้นอินสแตนซ์เท่านั้น เพื่อให้โค้ดมีทั้งถูกต้องและมีประสิทธิภาพ
นี่คือกลไกที่เรียกว่า "double check lock" (ตามชื่อที่แนะนำ)
น่าเสียดายที่วิธีการเขียนนี้ผิดในหลายแพลตฟอร์มและการปรับแต่งคอมไพเลอร์ให้เหมาะสม
เหตุผลก็คือพฤติกรรมของบรรทัดโค้ด instance = new Singleton() บนคอมไพเลอร์ที่แตกต่างกันนั้นไม่สามารถคาดเดาได้ คอมไพเลอร์ที่ปรับให้เหมาะสมที่สุดสามารถใช้ instance = new Singleton() ได้อย่างถูกกฎหมายดังนี้:
1. instance = จัดสรรหน่วยความจำให้กับเอนทิตีใหม่
2. เรียกตัวสร้าง Singleton เพื่อเตรียมใช้งานตัวแปรสมาชิกอินสแตนซ์
ตอนนี้ลองจินตนาการว่าเธรด A และ B กำลังเรียกใช้ getInstance เธรด A จะเข้ามาก่อนและจะถูกไล่ออกจาก CPU เมื่อดำเนินการขั้นตอนที่ 1 จากนั้นเธรด B ก็เข้ามา และสิ่งที่ B เห็นก็คืออินสแตนซ์นั้นไม่เป็นโมฆะอีกต่อไป (หน่วยความจำได้รับการจัดสรรแล้ว) จึงเริ่มใช้อินสแตนซ์ด้วยความมั่นใจ แต่มันผิด เพราะในขณะนี้ ตัวแปรสมาชิกของอินสแตนซ์ยังคงเป็นค่าเริ่มต้น ค่า A ยังไม่มีเวลาในการดำเนินการขั้นตอนที่ 2 เพื่อเริ่มต้นอินสแตนซ์ให้เสร็จสมบูรณ์
แน่นอนว่าคอมไพเลอร์สามารถนำไปใช้ได้ดังนี้:
1. temp = จัดสรรหน่วยความจำ
2. เรียกตัวสร้างอุณหภูมิ
3. อินสแตนซ์ = อุณหภูมิ
หากคอมไพเลอร์มีพฤติกรรมเช่นนี้ ดูเหมือนว่าเราจะไม่มีปัญหา แต่ความจริงแล้วมันไม่ง่ายอย่างนั้น เพราะเราไม่รู้ว่าคอมไพเลอร์บางตัวทำอย่างไร เนื่องจากปัญหานี้ไม่ได้ถูกกำหนดไว้ในโมเดลหน่วยความจำของ Java
การล็อคด้วยเช็คสองชั้นใช้ได้กับประเภทพื้นฐาน (เช่น int) แน่นอน เนื่องจากประเภทพื้นฐานไม่ได้เรียกตัวสร้าง