1. บทนำ
ในบทความสั้น ๆ นี้เราจะพูดถึงสองวิธียอดนิยมในการใช้ Singletons ใน Java ธรรมดา
2. ซิงเกิลตามคลาส
แนวทางที่ได้รับความนิยมมากที่สุดคือการใช้ Singleton โดยการสร้างคลาสปกติและตรวจสอบให้แน่ใจว่ามี:
- ตัวสร้างส่วนตัว
- ฟิลด์สแตติกที่มีอินสแตนซ์เดียว
- วิธีโรงงานแบบคงที่สำหรับการรับอินสแตนซ์
นอกจากนี้เราจะเพิ่มคุณสมบัติข้อมูลสำหรับการใช้งานในภายหลังเท่านั้น ดังนั้นการใช้งานของเราจะมีลักษณะดังนี้:
public final class ClassSingleton { private static ClassSingleton INSTANCE; private String info = "Initial info class"; private ClassSingleton() { } public static ClassSingleton getInstance() { if(INSTANCE == null) { INSTANCE = new ClassSingleton(); } return INSTANCE; } // getters and setters }
แม้ว่านี่จะเป็นวิธีการทั่วไป แต่สิ่งสำคัญคือต้องทราบว่าอาจเป็นปัญหาในสถานการณ์จำลองหลายเธรดซึ่งเป็นเหตุผลหลักในการใช้ Singletons
พูดง่ายๆก็คืออาจส่งผลมากกว่าหนึ่งอินสแตนซ์ซึ่งทำลายหลักการหลักของรูปแบบ แม้ว่าจะมีวิธีแก้ไขปัญหานี้ แต่แนวทางต่อไปของเราจะแก้ปัญหาเหล่านี้ในระดับราก
3. Enum Singleton
ในอนาคตเราจะไม่พูดถึงแนวทางอื่นที่น่าสนใจซึ่งก็คือการใช้การแจงนับ:
public enum EnumSingleton { INSTANCE("Initial class info"); private String info; private EnumSingleton(String info) { this.info = info; } public EnumSingleton getInstance() { return INSTANCE; } // getters and setters }
แนวทางนี้มีการทำให้เป็นอนุกรมและความปลอดภัยของเธรดที่รับประกันโดยการใช้งาน enum เองซึ่งทำให้มั่นใจได้ว่าภายในจะมีเพียงอินสแตนซ์เดียวเท่านั้นโดยจะแก้ไขปัญหาที่ระบุในการใช้งานตามคลาส
4. การใช้งาน
ในการใช้ClassSingletonของเราเราจำเป็นต้องรับอินสแตนซ์แบบคงที่:
ClassSingleton classSingleton1 = ClassSingleton.getInstance(); System.out.println(classSingleton1.getInfo()); //Initial class info ClassSingleton classSingleton2 = ClassSingleton.getInstance(); classSingleton2.setInfo("New class info"); System.out.println(classSingleton1.getInfo()); //New class info System.out.println(classSingleton2.getInfo()); //New class info
สำหรับEnumSingletonเราสามารถใช้งานได้เหมือนกับ Java Enum อื่น ๆ :
EnumSingleton enumSingleton1 = EnumSingleton.INSTANCE.getInstance(); System.out.println(enumSingleton1.getInfo()); //Initial enum info EnumSingleton enumSingleton2 = EnumSingleton.INSTANCE.getInstance(); enumSingleton2.setInfo("New enum info"); System.out.println(enumSingleton1.getInfo()); // New enum info System.out.println(enumSingleton2.getInfo()); // New enum info
5. ข้อผิดพลาดทั่วไป
Singleton เป็นรูปแบบการออกแบบที่เรียบง่ายหลอกลวงและมีข้อผิดพลาดทั่วไปบางประการที่โปรแกรมเมอร์อาจกระทำเมื่อสร้างซิงเกิลตัน
เราแยกแยะปัญหาสองประเภทด้วย singletons:
- อัตถิภาวนิยม (เราต้องการซิงเกิลตันหรือไม่?)
- การใช้งาน (เรานำไปใช้อย่างถูกต้องหรือไม่?)
5.1. ปัญหาที่มีอยู่
ตามแนวคิดแล้วซิงเกิลตันเป็นตัวแปรระดับโลกชนิดหนึ่ง โดยทั่วไปเราทราบดีว่าควรหลีกเลี่ยงตัวแปรทั่วโลก - โดยเฉพาะอย่างยิ่งหากสถานะของตัวแปรนั้นเปลี่ยนแปลงได้
เราไม่ได้บอกว่าเราไม่ควรใช้เสื้อกล้าม อย่างไรก็ตามเรากำลังบอกว่าอาจมีวิธีที่มีประสิทธิภาพมากกว่าในการจัดระเบียบโค้ดของเรา
หากการใช้งานเมธอดขึ้นอยู่กับอ็อบเจ็กต์เดี่ยวทำไมไม่ส่งเป็นพารามิเตอร์ ในกรณีนี้เราจะแสดงให้เห็นอย่างชัดเจนว่าวิธีนี้ขึ้นอยู่กับอะไร ด้วยเหตุนี้เราอาจล้อเลียนการอ้างอิงเหล่านี้ได้อย่างง่ายดาย (หากจำเป็น) เมื่อทำการทดสอบ
ตัวอย่างเช่นมักใช้ singletons เพื่อรวมข้อมูลคอนฟิกูเรชันของแอปพลิเคชัน (เช่นการเชื่อมต่อกับที่เก็บ) หากใช้เป็นวัตถุส่วนกลางจะเป็นการยากที่จะเลือกการกำหนดค่าสำหรับสภาพแวดล้อมการทดสอบ
ดังนั้นเมื่อเราเรียกใช้การทดสอบฐานข้อมูลการผลิตจะเสียไปด้วยข้อมูลการทดสอบซึ่งแทบจะไม่เป็นที่ยอมรับ
หากเราต้องการซิงเกิลตันเราอาจพิจารณาถึงความเป็นไปได้ในการมอบหมายการสร้างอินสแตนซ์ของมันไปยังคลาสอื่นซึ่งเป็นโรงงานประเภทหนึ่งซึ่งควรดูแลให้มั่นใจว่ามีซิงเกิลตันอยู่เพียงชุดเดียว
5.2. ประเด็นการดำเนินการ
แม้ว่าเสื้อกล้ามจะดูเรียบง่าย แต่การนำไปใช้งานอาจประสบปัญหาต่างๆ ทั้งหมดส่งผลให้เราอาจมีมากกว่าหนึ่งอินสแตนซ์ของชั้นเรียน
การซิงโครไนซ์
การใช้งานกับคอนสตรัคเตอร์ส่วนตัวที่เรานำเสนอข้างต้นไม่ปลอดภัยต่อเธรด: ทำงานได้ดีในสภาพแวดล้อมแบบเธรดเดียว แต่ในมัลติเธรดเราควรใช้เทคนิคการซิงโครไนซ์เพื่อรับประกันความเป็นอะตอมของการดำเนินการ:
public synchronized static ClassSingleton getInstance() { if (INSTANCE == null) { INSTANCE = new ClassSingleton(); } return INSTANCE; }
สังเกตคำสำคัญที่ซิงโครไนซ์ในการประกาศวิธีการ ร่างกายของวิธีการมีการดำเนินการหลายอย่าง (การเปรียบเทียบการสร้างอินสแตนซ์และการส่งคืน)
ในกรณีที่ไม่มีการซิงโครไนซ์มีความเป็นไปได้ที่เธรดสองชุดจะแทรกระหว่างการดำเนินการในลักษณะที่นิพจน์INSTANCE == nullประเมินเป็นจริงสำหรับเธรดทั้งสองและด้วยเหตุนี้จึงมีการสร้างClassSingletonสองอินสแตนซ์
การซิงโครไนซ์อาจส่งผลต่อประสิทธิภาพอย่างมาก หากมีการเรียกใช้รหัสนี้บ่อยครั้งเราควรเร่งความเร็วโดยใช้เทคนิคต่างๆเช่นการเริ่มต้นแบบขี้เกียจหรือการล็อกที่ตรวจสอบซ้ำ (โปรดทราบว่าอาจไม่ได้ผลตามที่คาดไว้เนื่องจากการเพิ่มประสิทธิภาพของคอมไพเลอร์) เราสามารถดูรายละเอียดเพิ่มเติมได้ในบทแนะนำเรื่อง "Double-Checked Locking with Singleton"
หลายอินสแตนซ์
มีปัญหาอื่น ๆ อีกหลายประการเกี่ยวกับ singleton ที่เกี่ยวข้องกับ JVM เองที่อาจทำให้เราต้องพบกับ Singleton หลายอินสแตนซ์ ปัญหาเหล่านี้ค่อนข้างละเอียดอ่อนและเราจะให้คำอธิบายสั้น ๆ สำหรับแต่ละประเด็น:
- Singleton ควรจะไม่ซ้ำกันต่อ JVM นี่อาจเป็นปัญหาสำหรับระบบแบบกระจายหรือระบบที่มีการใช้เทคโนโลยีแบบกระจายภายใน
- ตัวโหลดคลาสทุกตัวอาจโหลดเวอร์ชันของซิงเกิลตัน
- ซิงเกิลตันอาจถูกเก็บขยะเมื่อไม่มีใครอ้างอิงถึงมัน ปัญหานี้ไม่ได้นำไปสู่การมีอินสแตนซ์ซิงเกิลตันหลายรายการพร้อมกัน แต่เมื่อสร้างขึ้นใหม่อินสแตนซ์อาจแตกต่างจากเวอร์ชันก่อนหน้า
6. บทสรุป
ในบทช่วยสอนฉบับย่อนี้เรามุ่งเน้นไปที่วิธีการใช้งานรูปแบบ Singleton โดยใช้เฉพาะ Java หลักและวิธีทำให้แน่ใจว่าสอดคล้องกันและจะใช้ประโยชน์จากการใช้งานเหล่านี้ได้อย่างไร
ตัวอย่างการใช้งานทั้งหมดสามารถพบได้บน GitHub