วิธีที่มีประสิทธิภาพในการใช้รูปแบบซิงเกิลใน Java คืออะไร?
วิธีที่มีประสิทธิภาพในการใช้รูปแบบซิงเกิลใน Java คืออะไร?
คำตอบ:
ใช้ enum:
public enum Foo {
INSTANCE;
}
โจชัวโบลชอธิบายวิธีการนี้ในของเขามีผลบังคับใช้ Java Reloadedพูดคุยที่ Google I / O 2008: ลิงก์ไปยังวิดีโอ ดูสไลด์ 30-32 ของงานนำเสนอของเขา ( effective_java_reloaded.pdf ):
วิธีที่ถูกต้องในการติดตั้ง Singleton
public enum Elvis { INSTANCE; private final String[] favoriteSongs = { "Hound Dog", "Heartbreak Hotel" }; public void printFavorites() { System.out.println(Arrays.toString(favoriteSongs)); } }
แก้ไข: ส่วนออนไลน์ "มีผลบังคับใช้ Java"พูดว่า:
"วิธีนี้เทียบเท่ากับการใช้งานในพื้นที่สาธารณะยกเว้นว่ามีความรัดกุมมากกว่านั้นให้บริการการทำให้เป็นอนุกรมฟรีและให้การรับประกันที่แข็งแกร่งต่อการโต้ตอบหลายครั้งแม้ในการเผชิญหน้าต่อเนื่องที่ซับซ้อนหรือการสะท้อนการโจมตี ยังไม่ได้รับการยอมรับอย่างกว้างขวางประเภท enum องค์ประกอบเดียวเป็นวิธีที่ดีที่สุดในการใช้งานซิงเกิลตัน "
ขึ้นอยู่กับการใช้งานมีคำตอบ "ถูกต้อง" หลายคำ
ตั้งแต่ java5 วิธีที่ดีที่สุดในการทำคือใช้ enum:
public enum Foo {
INSTANCE;
}
ก่อน java5, กรณีที่ง่ายที่สุดคือ:
public final class Foo {
private static final Foo INSTANCE = new Foo();
private Foo() {
if (INSTANCE != null) {
throw new IllegalStateException("Already instantiated");
}
}
public static Foo getInstance() {
return INSTANCE;
}
public Object clone() throws CloneNotSupportedException{
throw new CloneNotSupportedException("Cannot clone instance of this class");
}
}
ไปดูรหัสกันดีกว่า ก่อนอื่นคุณต้องการให้ชั้นเรียนเป็นที่สิ้นสุด ในกรณีนี้ฉันใช้finalคำหลักเพื่อให้ผู้ใช้ทราบว่าเป็นที่สิ้นสุด จากนั้นคุณต้องสร้างคอนสตรัคเตอร์ส่วนตัวเพื่อป้องกันไม่ให้ผู้ใช้สร้าง Foo ของตัวเอง การโยนข้อยกเว้นจากตัวสร้างป้องกันผู้ใช้ใช้การสะท้อนเพื่อสร้าง Foo ตัวที่สอง จากนั้นคุณสร้างprivate static final Fooฟิลด์เพื่อเก็บอินสแตนซ์เท่านั้นและpublic static Foo getInstance()วิธีการส่งคืน สเปค Java ทำให้แน่ใจว่าคอนสตรัคเตอร์จะถูกเรียกเมื่อใช้คลาสเป็นครั้งแรกเท่านั้น
เมื่อคุณมีวัตถุที่มีขนาดใหญ่มากหรือรหัสการก่อสร้างจำนวนมากและยังมีวิธีการหรือฟิลด์แบบคงที่อื่น ๆ ที่สามารถใช้งานได้ก่อนที่จะต้องใช้อินสแตนซ์ดังนั้นคุณต้องใช้การเริ่มต้นแบบขี้เกียจเท่านั้น
คุณสามารถใช้ a private static classเพื่อโหลดอินสแตนซ์ รหัสจะมีลักษณะดังนี้:
public final class Foo {
private static class FooLoader {
private static final Foo INSTANCE = new Foo();
}
private Foo() {
if (FooLoader.INSTANCE != null) {
throw new IllegalStateException("Already instantiated");
}
}
public static Foo getInstance() {
return FooLoader.INSTANCE;
}
}
เนื่องจากบรรทัดprivate static final Foo INSTANCE = new Foo();จะถูกดำเนินการเมื่อใช้คลาส FooLoader จริงเท่านั้นนี่จะดูแลการสร้างอินสแตนซ์ที่ขี้เกียจและรับประกันว่าจะปลอดภัยต่อเธรด
เมื่อคุณต้องการทำให้วัตถุของคุณเป็นอนุกรมคุณต้องตรวจสอบให้แน่ใจว่าการดีซีเรียลไลซ์เซชั่นจะไม่สร้างสำเนา
public final class Foo implements Serializable {
private static final long serialVersionUID = 1L;
private static class FooLoader {
private static final Foo INSTANCE = new Foo();
}
private Foo() {
if (FooLoader.INSTANCE != null) {
throw new IllegalStateException("Already instantiated");
}
}
public static Foo getInstance() {
return FooLoader.INSTANCE;
}
@SuppressWarnings("unused")
private Foo readResolve() {
return FooLoader.INSTANCE;
}
}
วิธีการreadResolve()นี้จะทำให้แน่ใจว่าจะส่งคืนอินสแตนซ์เดียวเท่านั้นแม้ว่าวัตถุนั้นจะได้รับการทำให้เป็นอนุกรมในการรันโปรแกรมก่อนหน้านี้ของคุณก็ตาม
ข้อจำกัดความรับผิดชอบ:ฉันเพิ่งสรุปคำตอบที่ยอดเยี่ยมทั้งหมดและเขียนไว้ในคำพูดของฉัน
ในขณะที่ใช้งานซิงเกิลตันเรามี 2 ตัวเลือก
1. การโหลดขี้เกียจ
2. การโหลดก่อนหน้า
การโหลดขี้เกียจเพิ่มค่าใช้จ่ายบิต (จำนวนมากจะซื่อสัตย์) ดังนั้นให้ใช้เฉพาะเมื่อคุณมีวัตถุที่มีขนาดใหญ่มากหรือรหัสการก่อสร้างหนักและยังมีวิธีการคงที่อื่น ๆ ที่สามารถเข้าถึงได้หรืออาจจะใช้ก่อนที่จะอินสแตนซ์แล้วเท่านั้น คุณต้องใช้การเริ่มต้นขี้เกียจมิฉะนั้นการเลือกโหลดก่อนหน้านั้นเป็นตัวเลือกที่ดี
วิธีที่ง่ายที่สุดในการติดตั้งซิงเกิลตันคือ
public class Foo {
// It will be our sole hero
private static final Foo INSTANCE = new Foo();
private Foo() {
if (INSTANCE != null) {
// SHOUT
throw new IllegalStateException("Already instantiated");
}
}
public static Foo getInstance() {
return INSTANCE;
}
}
ทุกอย่างดียกเว้นซิงเกิลที่โหลดตอนต้น ให้ลองสันหลังยาวโหลดขี้เกียจ
class Foo {
// Our now_null_but_going_to_be sole hero
private static Foo INSTANCE = null;
private Foo() {
if (INSTANCE != null) {
// SHOUT
throw new IllegalStateException("Already instantiated");
}
}
public static Foo getInstance() {
// Creating only when required.
if (INSTANCE == null) {
INSTANCE = new Foo();
}
return INSTANCE;
}
}
จนถึงตอนนี้ดีมาก แต่ฮีโร่ของเราจะไม่รอดในขณะที่ต่อสู้เพียงลำพังด้วยเธรดความชั่วร้ายมากมายที่ต้องการตัวอย่างฮีโร่ของเรามากมาย ดังนั้นช่วยป้องกันจากเกลียวชั่วร้าย
class Foo {
private static Foo INSTANCE = null;
// TODO Add private shouting constructor
public static Foo getInstance() {
// No more tension of threads
synchronized (Foo.class) {
if (INSTANCE == null) {
INSTANCE = new Foo();
}
}
return INSTANCE;
}
}
แต่มันก็ไม่เพียงพอที่จะปกป้องฮีโร่ได้จริง ๆ !!! นี่คือสิ่งที่ดีที่สุดที่เราสามารถทำได้ / ควรทำเพื่อช่วยเหลือฮีโร่ของเรา
class Foo {
// Pay attention to volatile
private static volatile Foo INSTANCE = null;
// TODO Add private shouting constructor
public static Foo getInstance() {
if (INSTANCE == null) { // Check 1
synchronized (Foo.class) {
if (INSTANCE == null) { // Check 2
INSTANCE = new Foo();
}
}
}
return INSTANCE;
}
}
สิ่งนี้เรียกว่า "สำนวนที่ต้องตรวจสอบอีกครั้ง" มันง่ายที่จะลืมข้อความที่ระเหยง่ายและยากที่จะเข้าใจว่าทำไมจึงมีความจำเป็น
สำหรับรายละเอียด: http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html
ตอนนี้เรามีความมั่นใจเกี่ยวกับด้ายชั่วร้าย แต่สิ่งที่เกี่ยวกับการเป็นอันดับที่โหดร้าย? เราต้องทำให้แน่ใจว่าแม้ในขณะที่ไม่มีการสร้างวัตถุใหม่
class Foo implements Serializable {
private static final long serialVersionUID = 1L;
private static volatile Foo INSTANCE = null;
// Rest of the things are same as above
// No more fear of serialization
@SuppressWarnings("unused")
private Object readResolve() {
return INSTANCE;
}
}
วิธีการreadResolve()จะทำให้แน่ใจว่าอินสแตนซ์เดียวเท่านั้นที่จะถูกส่งคืนแม้ว่าวัตถุนั้นจะได้รับการทำให้เป็นอนุกรมในการรันครั้งก่อนหน้าของโปรแกรมของเรา
ในที่สุดเราได้เพิ่มการป้องกันเธรดและการทำให้เป็นอนุกรมมากพอ แต่โค้ดของเราดูใหญ่และน่าเกลียด ช่วยให้ฮีโร่ของเราได้รับชัยชนะ
public final class Foo implements Serializable {
private static final long serialVersionUID = 1L;
// Wrapped in a inner static class so that loaded only when required
private static class FooLoader {
// And no more fear of threads
private static final Foo INSTANCE = new Foo();
}
// TODO add private shouting construcor
public static Foo getInstance() {
return FooLoader.INSTANCE;
}
// Damn you serialization
@SuppressWarnings("unused")
private Foo readResolve() {
return FooLoader.INSTANCE;
}
}
ใช่นี่คือฮีโร่ที่เหมือนกันของเรา :)
เนื่องจากบรรทัดprivate static final Foo INSTANCE = new Foo();จะถูกประหารชีวิตเมื่อมีการใช้ชั้นเรียนFooLoaderจริงเท่านั้นซึ่งจะดูแลการเริ่มต้นที่ขี้เกียจ
และรับประกันว่าปลอดภัยไหม
และเราได้มาถึงแล้วนี่คือวิธีที่ดีที่สุดในการบรรลุทุกสิ่งที่เราทำคือวิธีที่ดีที่สุด
public enum Foo {
INSTANCE;
}
ซึ่งภายในจะได้รับการปฏิบัติเหมือน
public class Foo {
// It will be our sole hero
private static final Foo INSTANCE = new Foo();
}
แค่นั้นแหละ! ไม่ต้องกลัวว่าจะมีการทำให้เป็นอนุกรมเธรดและรหัสที่น่าเกลียดอีกต่อไป นอกจากนี้ยังenums เดี่ยวจะเริ่มต้นอย่างเฉื่อยชา
วิธีนี้เทียบเท่ากับการใช้งานในพื้นที่สาธารณะยกเว้นว่ามีความรัดกุมมากกว่านี้ให้การจัดลำดับของเครื่องจักรฟรีและให้การรับประกันที่แข็งแกร่งต่อการโต้ตอบหลายครั้งแม้ในการเผชิญกับการโจมตีต่อเนื่องที่ซับซ้อน ในขณะที่วิธีนี้ยังไม่ได้รับการยอมรับอย่างกว้างขวางประเภท enum องค์ประกอบเดียวเป็นวิธีที่ดีที่สุดในการใช้ซิงเกิล
-Joshua Bloch ใน "Java ที่มีประสิทธิภาพ"
ตอนนี้คุณอาจได้ตระหนักว่าทำไม enums จะถือว่าเป็นวิธีที่ดีที่สุดที่จะใช้โทนและขอขอบคุณสำหรับความอดทนของคุณ :)
Updated มันบนของบล็อก
serialVersionUID 0Lปัญหาที่สาม: ไม่มีการกำหนดเอง: เมธอด writeObject, classObject, readObject, readObjectNoData, writeReplace และ readResolve ใด ๆ ที่ระบุเฉพาะจะถูกละเว้นระหว่างการทำให้เป็นอนุกรมและ
วิธีการแก้ปัญหาที่โพสต์โดย Stu Thompson นั้นถูกต้องใน Java5.0 และใหม่กว่า แต่ฉันไม่ต้องการใช้เพราะฉันคิดว่ามันผิดพลาดได้ง่าย
มันง่ายที่จะลืมข้อความที่ระเหยง่ายและยากที่จะเข้าใจว่าทำไมจึงมีความจำเป็น หากไม่มีการระเหยรหัสนี้จะไม่ปลอดภัยเธรดอีกต่อไปเนื่องจาก antipattern ล็อคการตรวจสอบสองครั้ง ดูข้อมูลเพิ่มเติมเกี่ยวกับเรื่องนี้ในวรรค 16.2.4 ของJava Concurrency ในการปฏิบัติ ในระยะสั้น: รูปแบบนี้ (ก่อนหน้า Java5.0 หรือไม่มีคำสั่งระเหย) สามารถกลับการอ้างอิงไปยังวัตถุบาร์ที่ (ยังคง) ในสถานะที่ไม่ถูกต้อง
รูปแบบนี้ถูกคิดค้นเพื่อการเพิ่มประสิทธิภาพ แต่นี่ไม่ใช่เรื่องจริงอีกต่อไป โค้ดการเริ่มต้นขี้เกียจต่อไปนี้เป็นไปอย่างรวดเร็วและที่สำคัญยิ่งกว่า - อ่านได้ง่ายขึ้น
class Bar {
private static class BarHolder {
public static Bar bar = new Bar();
}
public static Bar getBar() {
return BarHolder.bar;
}
}
getBar()แต่ก็จะไม่ได้รับการเรียกจนกว่าแรก (และถ้าgetBarเรียกว่า "เร็วเกินไป" คุณจะพบปัญหาเดียวกันไม่ว่าจะใช้งานแบบ Singleons อย่างไร) คุณสามารถดูการโหลดคลาสที่ขี้เกียจของโค้ดด้านบนได้ที่นี่: pastebin.com/iq2eayiR
หัวข้อความปลอดภัยใน Java 5+:
class Foo {
private static volatile Bar bar = null;
public static Bar getBar() {
if (bar == null) {
synchronized(Foo.class) {
if (bar == null)
bar = new Bar();
}
}
return bar;
}
}
แก้ไข : ให้ความสนใจกับvolatileตัวดัดแปลงที่นี่ :) เป็นสิ่งสำคัญเพราะหากไม่ได้กำหนดไว้เธรดอื่น ๆ จะไม่รับประกันโดย JMM (Java Memory Model) เพื่อดูการเปลี่ยนแปลงค่าของมัน การซิงโครไนซ์ไม่ได้ดูแลสิ่งนั้น - เพียงทำให้การเข้าถึงรหัสบล็อกนั้นเป็นอนุกรม
แก้ไข 2 : @Bno ตอบรายละเอียดวิธีการที่แนะนำโดย Bill Pugh (FindBugs) และพิสูจน์ได้ดีขึ้น ไปอ่านและโหวตคำตอบของเขาด้วย
ลืมการเริ่มต้นขี้เกียจมันมีปัญหาเกินไป นี่คือทางออกที่ง่ายที่สุด:
public class A {
private static final A INSTANCE = new A();
private A() {}
public static A getInstance() {
return INSTANCE;
}
}
ตรวจสอบให้แน่ใจว่าคุณต้องการมันจริงๆ ทำ google สำหรับ "singleton anti-pattern" เพื่อดูข้อโต้แย้งบางอย่าง ไม่มีอะไรผิดปกติกับมันฉันคิดว่า แต่มันเป็นเพียงกลไกสำหรับการเปิดเผยทรัพยากร / ข้อมูลทั่วโลกบางส่วนเพื่อให้แน่ใจว่านี่เป็นวิธีที่ดีที่สุด โดยเฉพาะอย่างยิ่งฉันพบว่าการพึ่งพาการฉีดมีประโยชน์มากขึ้นโดยเฉพาะถ้าคุณใช้การทดสอบหน่วยเพราะ DI ช่วยให้คุณสามารถใช้ทรัพยากรจำลองเพื่อการทดสอบ
ฉันประหลาดใจกับคำตอบบางข้อที่แนะนำ DI เป็นทางเลือกในการใช้ซิงเกิล นี่เป็นแนวคิดที่ไม่เกี่ยวข้อง คุณสามารถใช้ DI เพื่อฉีดอินสแตนซ์ทั้งแบบซิงเกิลหรือแบบที่ไม่ใช่ซิงเกิล (เช่นต่อเธรด) อย่างน้อยก็เป็นจริงถ้าคุณใช้ Spring 2.x ฉันไม่สามารถพูดกับ DI อื่น ๆ ได้
ดังนั้นคำตอบของฉันเกี่ยวกับ OP น่าจะเป็น (ในทุกตัวอย่างยกเว้นโค้ดตัวอย่างที่น่าสนใจที่สุด):
วิธีการนี้ช่วยให้คุณมีสถาปัตยกรรม decoupled ที่ดี (และมีความยืดหยุ่นและสามารถทดสอบได้) โดยที่ไม่ว่าจะใช้ซิงเกิลตันนั้นเป็นรายละเอียดการใช้งานที่สามารถย้อนกลับได้อย่างง่ายดาย (หากมีซิงเกิลที่คุณใช้นั้นเป็น threadsafe
TicketNumbererซึ่งจะต้องมีเช่นเดียวทั่วโลกและสถานที่ที่คุณต้องการเขียนชั้นเรียนซึ่งมีบรรทัดของรหัสTicketIssuer int ticketNumber = ticketNumberer.nextTicketNumber();ในการคิดแบบซิงเกิลดั้งเดิมบรรทัดของโค้ดจะต้องมีลักษณะTicketNumberer ticketNumberer = TicketNumberer.INSTANCE;ดังนี้ ใน DI public TicketIssuer(TicketNumberer ticketNumberer) { this.ticketNumberer = ticketNumberer; }คิดชั้นเรียนจะมีคอนสตรัคเหมือน
mainวิธีการของแอป(หรือหนึ่งในลูกน้องของมัน) จะสร้างการพึ่งพาและจากนั้นเรียกตัวสร้าง โดยพื้นฐานแล้วการใช้ตัวแปรทั่วโลก (หรือวิธีการทั่วโลก) เป็นเพียงรูปแบบที่เรียบง่ายของรูปแบบบริการระบุตำแหน่งที่หวั่นและสามารถถูกแทนที่ด้วยการฉีดพึ่งพาเช่นเดียวกับการใช้รูปแบบอื่น ๆ
พิจารณาเหตุผลที่คุณต้องการซิงเกิลตันก่อนที่จะเขียน มีการถกเถียงกันแบบกึ่งศาสนาเกี่ยวกับการใช้สิ่งเหล่านี้ซึ่งคุณสามารถสะดุดได้ง่ายถ้าคุณใช้ google singletons ใน Java
โดยส่วนตัวแล้วฉันพยายามหลีกเลี่ยงซิงเกิลตันบ่อยที่สุดเท่าที่จะเป็นไปได้ด้วยหลายสาเหตุ ฉันรู้สึกว่าบ่อยครั้งที่ถูกทารุณกรรมเพราะพวกเขาเข้าใจง่ายโดยทุกคนพวกเขาใช้เป็นกลไกในการรับข้อมูล "ทั่วโลก" ในการออกแบบ OO และใช้เพราะมันง่ายต่อการหลีกเลี่ยงการจัดการวงจรชีวิตของวัตถุ (หรือ จริงๆคิดเกี่ยวกับวิธีที่คุณสามารถทำ A จากภายใน B) ดูสิ่งต่าง ๆ เช่น Inversion of Control (IoC) หรือ Dependency Injection (DI) สำหรับพื้นกลางที่สวยงาม
หากคุณต้องการวิกิพีเดียจริง ๆ ก็มีตัวอย่างที่ดีเกี่ยวกับการใช้ซิงเกิลที่เหมาะสม
ต่อไปนี้เป็น 3 วิธีที่แตกต่าง
1) Enum
/**
* Singleton pattern example using Java Enumj
*/
public enum EasySingleton{
INSTANCE;
}
2) ตรวจสอบอีกครั้งล็อค / โหลดขี้เกียจ
/**
* Singleton pattern example with Double checked Locking
*/
public class DoubleCheckedLockingSingleton{
private static volatile DoubleCheckedLockingSingleton INSTANCE;
private DoubleCheckedLockingSingleton(){}
public static DoubleCheckedLockingSingleton getInstance(){
if(INSTANCE == null){
synchronized(DoubleCheckedLockingSingleton.class){
//double checking Singleton instance
if(INSTANCE == null){
INSTANCE = new DoubleCheckedLockingSingleton();
}
}
}
return INSTANCE;
}
}
3) วิธีการโรงงานแบบคงที่
/**
* Singleton pattern example with static factory method
*/
public class Singleton{
//initailzed during class loading
private static final Singleton INSTANCE = new Singleton();
//to prevent creating another instance of Singleton
private Singleton(){}
public static Singleton getSingleton(){
return INSTANCE;
}
}
ฉันใช้ Spring Framework เพื่อจัดการซิงเกิลของฉัน มันไม่ได้บังคับใช้ "singleton-ness" ของคลาส (ซึ่งคุณไม่สามารถทำได้จริง ๆ ถ้ามีตัวโหลดคลาสหลายตัวที่เกี่ยวข้อง) แต่มีวิธีง่าย ๆ ในการสร้างและกำหนดค่าโรงงานที่แตกต่างกันสำหรับการสร้างวัตถุประเภทต่างๆ
Wikipedia มีตัวอย่างของ singletons เช่นเดียวกับใน Java การปรับใช้ Java 5 ดูค่อนข้างสมบูรณ์และมีความปลอดภัยในการใช้เธรด
รุ่น 1:
public class MySingleton {
private static MySingleton instance = null;
private MySingleton() {}
public static synchronized MySingleton getInstance() {
if(instance == null) {
instance = new MySingleton();
}
return instance;
}
}
synchronizedโหลดขี้เกียจปลอดภัยหัวข้อที่มีการปิดกั้นประสิทธิภาพการทำงานต่ำเพราะ
รุ่น 2:
public class MySingleton {
private MySingleton() {}
private static class MySingletonHolder {
public final static MySingleton instance = new MySingleton();
}
public static MySingleton getInstance() {
return MySingletonHolder.instance;
}
}
โหลดขี้เกียจด้ายปลอดภัยด้วยการไม่บล็อกประสิทธิภาพสูง
หากคุณไม่ต้องการโหลดขี้เกียจก็ลอง
public class Singleton {
private final static Singleton INSTANCE = new Singleton();
private Singleton() {}
public static Singleton getInstance() { return Singleton.INSTANCE; }
protected Object clone() {
throw new CloneNotSupportedException();
}
}
หากคุณต้องการให้โหลดแบบขี้เกียจและต้องการให้ซิงเกิลของคุณปลอดภัยกับด้ายลองรูปแบบการตรวจสอบซ้ำ
public class Singleton {
private static Singleton instance = null;
private Singleton() {}
public static Singleton getInstance() {
if(null == instance) {
synchronized(Singleton.class) {
if(null == instance) {
instance = new Singleton();
}
}
}
return instance;
}
protected Object clone() {
throw new CloneNotSupportedException();
}
}
เนื่องจากรูปแบบการตรวจสอบซ้ำซ้อนไม่รับประกันว่าจะทำงานได้ (เนื่องจากปัญหาเกี่ยวกับคอมไพเลอร์ฉันไม่ทราบอะไรมากไปกว่านั้น) คุณสามารถลองซิงโครไนซ์ทั้ง getInstance-method หรือสร้างรีจิสตรีสำหรับซิงเกิลทั้งหมดของคุณ
volatile
ฉันอยากจะพูดว่าเอนุมซิงเกิล
Singleton ใช้ enum ใน Java เป็นวิธีการประกาศ enum singleton Enum singleton อาจมีตัวแปรอินสแตนซ์และวิธีการอินสแตนซ์ เพื่อประโยชน์ของความเรียบง่ายโปรดทราบว่าหากคุณใช้วิธีการอินสแตนซ์ใด ๆ เกินกว่าที่คุณต้องการเพื่อความมั่นใจในความปลอดภัยของเธรดของวิธีการนั้น
การใช้ enum นั้นง่ายมากที่จะนำไปใช้และไม่มีข้อบกพร่องเกี่ยวกับวัตถุที่เป็นอนุกรมซึ่งจะต้องหลีกเลี่ยงในวิธีอื่น
/**
* Singleton pattern example using Java Enum
*/
public enum Singleton {
INSTANCE;
public void execute (String arg) {
//perform operation here
}
}
คุณสามารถเข้าถึงได้โดยSingleton.INSTANCEง่ายกว่าgetInstance()วิธีการโทรบน Singleton
1.12 การทำให้เป็นค่าคงที่ของ Enum
Enum ค่าคงที่จะถูกจัดลำดับแตกต่างจากวัตถุที่สามารถ รูปแบบต่อเนื่องของค่าคงที่ enum ประกอบด้วยชื่อ แต่เพียงผู้เดียว ค่าฟิลด์ของค่าคงที่ไม่ปรากฏในรูปแบบ ในการทำให้ค่าคงที่ enum เป็นอนุกรมให้
ObjectOutputStreamเขียนค่าที่ส่งคืนโดยเมธอด name ของค่าคงที่ enum ในการดีซีเรียลไลซ์ค่าคงที่ enum ให้ObjectInputStreamอ่านชื่อค่าคงที่จากสตรีม deserialized คงที่นั้นได้รับโดยการเรียกjava.lang.Enum.valueOfวิธีการผ่านประเภท enum คงที่พร้อมกับชื่อค่าคงที่ได้รับเป็นข้อโต้แย้ง เช่นเดียวกับวัตถุอื่น ๆ ที่สามารถทำให้เป็นอนุกรมหรือภายนอกได้ Enum ค่าคงที่สามารถทำหน้าที่เป็นเป้าหมายของการอ้างอิงด้านหลังที่ปรากฏในภายหลังในกระแสอนุกรมกระบวนการที่คงที่ enum จะต่อเนื่องไม่สามารถปรับแต่ง: ระดับใด ๆ ที่เฉพาะเจาะจง
writeObject,readObject,readObjectNoData,writeReplaceและreadResolveวิธีการที่กำหนดไว้ตามประเภท enum จะถูกละเลยในช่วงอนุกรมและ deserialization ในทำนองเดียวกันใด ๆserialPersistentFieldsหรือserialVersionUIDข้อมูลการประกาศยังถูกละเลย - ทุกประเภท enum ได้คงที่ของserialVersionUID0Lการทำเอกสารฟิลด์และข้อมูลที่เรียงลำดับได้สำหรับประเภท enum ไม่จำเป็นเนื่องจากไม่มีการเปลี่ยนแปลงในประเภทของข้อมูลที่ส่ง
ปัญหาอีกประการหนึ่งของ Singletons ทั่วไปคือเมื่อคุณใช้Serializableส่วนต่อประสานพวกมันจะไม่เหลือ Singleton อีกต่อไปเพราะreadObject()เมธอดจะส่งคืนอินสแตนซ์ใหม่เช่น Constructor ใน Java สิ่งนี้สามารถหลีกเลี่ยงได้โดยใช้readResolve()และละทิ้งอินสแตนซ์ที่สร้างขึ้นใหม่โดยแทนที่ด้วยซิงเกิลตันด้านล่าง
// readResolve to prevent another instance of Singleton
private Object readResolve(){
return INSTANCE;
}
สิ่งนี้อาจมีความซับซ้อนมากยิ่งขึ้นถ้าคลาส Singleton ของคุณรักษาสถานะไว้ตามที่คุณต้องการเพื่อให้เป็นแบบชั่วคราว แต่ด้วย Enum Singleton การรับประกันการทำให้เป็นอนุกรมโดย JVM
อ่านดี
There are 4 ways to create a singleton in java.
1- eager initialization singleton
public class Test{
private static final Test test = new Test();
private Test(){}
public static Test getTest(){
return test;
}
}
2- lazy initialization singleton (thread safe)
public class Test {
private static volatile Test test;
private Test(){}
public static Test getTest() {
if(test == null) {
synchronized(Test.class) {
if(test == null){test = new Test();
}
}
}
return test;
}
3- Bill Pugh Singleton with Holder Pattern (Preferably the best one)
public class Test {
private Test(){}
private static class TestHolder{
private static final Test test = new Test();
}
public static Test getInstance(){
return TestHolder.test;
}
}
4- enum singleton
public enum MySingleton {
INSTANCE;
private MySingleton() {
System.out.println("Here");
}
}
อาจจะช้าไปหน่อยสำหรับเกมนี้ แต่มีความแตกต่างกันนิดหน่อยในการใช้ซิงเกิล รูปแบบตัวยึดไม่สามารถใช้ได้ในหลาย ๆ สถานการณ์ และ IMO เมื่อใช้สารระเหย - คุณควรใช้ตัวแปรท้องถิ่น เริ่มกันที่จุดเริ่มต้นแล้ววนปัญหา คุณจะเห็นสิ่งที่ฉันหมายถึง
ความพยายามครั้งแรกอาจมีลักษณะเช่นนี้:
public class MySingleton {
private static MySingleton INSTANCE;
public static MySingleton getInstance() {
if (INSTANCE == null) {
INSTANCE = new MySingleton();
}
return INSTANCE;
}
...
}
ที่นี่เรามีคลาส MySingleton ซึ่งมีสมาชิกคงที่ส่วนตัวที่เรียกว่า INSTANCE และวิธีคงที่สาธารณะที่เรียกว่า getInstance () ครั้งแรกที่เรียกว่า getInstance () สมาชิก INSTANCE นั้นเป็นโมฆะ การไหลจะตกอยู่ในสภาพการสร้างและสร้างอินสแตนซ์ใหม่ของคลาส MySingleton การเรียกใช้ getInstance () ในลำดับต่อมาจะพบว่าตัวแปร INSTANCE ถูกตั้งค่าไว้แล้วดังนั้นจึงไม่สร้างอินสแตนซ์ MySingleton อื่น สิ่งนี้ทำให้มั่นใจได้ว่ามี MySingleton เพียงอินสแตนซ์เดียวเท่านั้นที่แชร์กับผู้โทรทั้งหมดของ getInstance ()
แต่การใช้งานนี้มีปัญหา แอปพลิเคชันแบบมัลติเธรดจะมีเงื่อนไขการแข่งขันในการสร้างอินสแตนซ์เดียว หากมีการดำเนินการหลายเธรดให้ใช้เมธอด getInstance () ที่ (หรือใกล้เคียง) ในเวลาเดียวกันพวกเขาแต่ละคนจะเห็นสมาชิก INSTANCE เป็นโมฆะ สิ่งนี้จะส่งผลให้แต่ละเธรดสร้างอินสแตนซ์ MySingleton ใหม่จากนั้นตั้งค่าสมาชิก INSTANCE
private static MySingleton INSTANCE;
public static synchronized MySingleton getInstance() {
if (INSTANCE == null) {
INSTANCE = new MySingleton();
}
return INSTANCE;
}
ที่นี่เราใช้คำหลักที่ซิงโครไนซ์ในลายเซ็นเมธอดเพื่อซิงโครไนซ์เมธอด getInstance () สิ่งนี้จะแก้ไขสภาพการแข่งขันของเราอย่างแน่นอน ตอนนี้เธรดจะบล็อกและป้อนวิธีหนึ่งครั้ง แต่มันก็สร้างปัญหาด้านประสิทธิภาพด้วย การปรับใช้นี้ไม่เพียง แต่ซิงโครไนซ์การสร้างอินสแตนซ์เดียวเท่านั้น แต่ยังซิงโครไนซ์การโทรทั้งหมดเพื่อ getInstance () รวมถึงการอ่าน การอ่านไม่จำเป็นต้องซิงโครไนซ์เพราะพวกเขาเพียงแค่คืนค่า INSTANCE เนื่องจากการอ่านจะทำให้การโทรของเราเป็นจำนวนมาก (โปรดจำไว้ว่าการสร้างอินสแตนซ์จะเกิดขึ้นเฉพาะในการโทรครั้งแรกเท่านั้น) เราจะได้รับประสิทธิภาพที่ไม่จำเป็นโดยการซิงโครไนซ์วิธีการทั้งหมด
private static MySingleton INSTANCE;
public static MySingleton getInstance() {
if (INSTANCE == null) {
synchronize(MySingleton.class) {
INSTANCE = new MySingleton();
}
}
return INSTANCE;
}
ที่นี่เราได้ย้ายการซิงโครไนซ์จากลายเซ็นเมธอดไปยังบล็อกที่ซิงโครไนซ์ที่ล้อมรอบการสร้างอินสแตนซ์ MySingleton แต่นี่จะช่วยแก้ปัญหาของเราหรือไม่? เราไม่ได้บล็อกการอ่านอีกต่อไป แต่เราก็ก้าวถอยหลังไปอีกขั้น หลายเธรดจะเข้าสู่เมธอด getInstance () ที่หรือในเวลาใกล้เคียงกันและพวกเขาทั้งหมดจะเห็นสมาชิก INSTANCE เป็นโมฆะ พวกเขาจะกดบล็อกซิงโครไนซ์ที่หนึ่งจะได้รับการล็อคและสร้างอินสแตนซ์ เมื่อเธรดนั้นออกจากบล็อกเธรดอื่นจะต่อสู้เพื่อล็อกและทีละเธรดแต่ละเธรดจะผ่านบล็อกและสร้างอินสแตนซ์ใหม่ของคลาสของเรา ดังนั้นเราจะกลับมาที่จุดเริ่มต้น
private static MySingleton INSTANCE;
public static MySingleton getInstance() {
if (INSTANCE == null) {
synchronized(MySingleton.class) {
if (INSTANCE == null) {
INSTANCE = createInstance();
}
}
}
return INSTANCE;
}
ที่นี่เราออกการตรวจสอบอีกครั้งจากภายในบล็อก หากมีการตั้งค่าสมาชิก INSTANCE แล้วเราจะข้ามการเริ่มต้น สิ่งนี้เรียกว่าการล็อคซ้ำซ้อน
วิธีนี้จะช่วยแก้ปัญหาของเราในการสร้างอินสแตนซ์ได้หลายอย่าง แต่อีกครั้งทางออกของเราได้นำเสนอความท้าทายอีกครั้ง หัวข้ออื่น ๆ อาจไม่“ เห็น” ว่าสมาชิก INSTANCE ได้รับการอัปเดตแล้ว นี่เป็นเพราะวิธีการที่จาวาเพิ่มประสิทธิภาพการทำงานของหน่วยความจำ เธรดคัดลอกค่าดั้งเดิมของตัวแปรจากหน่วยความจำหลักลงในแคชของ CPU จากนั้นการเปลี่ยนแปลงค่าจะถูกเขียนไปยังและอ่านจากแคชนั้น นี่คือคุณสมบัติของ Java ที่ออกแบบมาเพื่อเพิ่มประสิทธิภาพ แต่สิ่งนี้สร้างปัญหาสำหรับการนำซิงเกิลตันไปใช้ เธรดที่สองที่ถูกประมวลผลโดย CPU หรือคอร์ที่ต่างกันโดยใช้แคชที่แตกต่างกันจะไม่เห็นการเปลี่ยนแปลงที่เกิดขึ้นในครั้งแรก สิ่งนี้จะทำให้เธรดที่สองเห็นสมาชิก INSTANCE เป็นโมฆะบังคับให้อินสแตนซ์ใหม่ของซิงเกิลของเราถูกสร้างขึ้น
private static volatile MySingleton INSTANCE;
public static MySingleton getInstance() {
if (INSTANCE == null) {
synchronized(MySingleton.class) {
if (INSTANCE == null) {
INSTANCE = createInstance();
}
}
}
return INSTANCE;
}
เราแก้ปัญหานี้โดยใช้คำสำคัญระเหยในการประกาศของสมาชิกทันที สิ่งนี้จะบอกคอมไพเลอร์ให้อ่านจากและเขียนไปยังหน่วยความจำหลักเสมอไม่ใช่ CPU แคช
แต่การเปลี่ยนแปลงง่ายๆนี้มีค่าใช้จ่าย เนื่องจากเรากำลังข้ามแคชของ CPU เราจะทำการแสดงผลทุกครั้งที่ทำงานกับสมาชิก INSTANCE ซึ่งระเหยได้ - ซึ่งเราทำ 4 ครั้ง เราตรวจสอบการมีอยู่จริง (1 และ 2) ตั้งค่า (3) จากนั้นคืนค่า (4) หนึ่งอาจโต้แย้งว่าเส้นทางนี้เป็นกรณีที่ขอบในขณะที่เราสร้างอินสแตนซ์เฉพาะในระหว่างการโทรครั้งแรกของวิธีการ บางทีประสิทธิภาพในการสร้างอาจเป็นที่ยอมรับได้ แต่ถึงแม้จะเป็นกรณีการใช้งานหลักของเราอ่านก็จะทำงานกับสมาชิกระเหยสองครั้ง หนึ่งครั้งเพื่อตรวจสอบการมีอยู่และอีกครั้งเพื่อคืนค่า
private static volatile MySingleton INSTANCE;
public static MySingleton getInstance() {
MySingleton result = INSTANCE;
if (result == null) {
synchronized(MySingleton.class) {
result = INSTANCE;
if (result == null) {
INSTANCE = result = createInstance();
}
}
}
return result;
}
เนื่องจากผลการดำเนินงานเกิดจากการทำงานโดยตรงกับสมาชิกที่มีความผันผวนขอตั้งค่าตัวแปรท้องถิ่นเป็นค่าของความผันผวนและดำเนินการกับตัวแปรท้องถิ่นแทน สิ่งนี้จะลดจำนวนครั้งที่เราดำเนินการกับความผันผวนดังนั้นจึงเรียกคืนประสิทธิภาพที่หายไปของเรา โปรดทราบว่าเราต้องตั้งค่าตัวแปรท้องถิ่นของเราอีกครั้งเมื่อเราเข้าบล็อกซิงโครไนซ์ สิ่งนี้ทำให้แน่ใจว่าเป็นข้อมูลล่าสุดเกี่ยวกับการเปลี่ยนแปลงใด ๆ ที่เกิดขึ้นในขณะที่เรากำลังรอการล็อก
ฉันเขียนบทความเกี่ยวกับเรื่องนี้เมื่อเร็ว ๆ นี้ Deconstructing ซิงเกิล คุณสามารถค้นหาข้อมูลเพิ่มเติมเกี่ยวกับตัวอย่างเหล่านี้และตัวอย่างของรูปแบบ "ผู้ถือ" ที่นั่น นอกจากนี้ยังมีตัวอย่างในโลกแห่งความจริงที่แสดงวิธีการระเหยที่ตรวจสอบซ้ำอีกครั้ง หวังว่านี่จะช่วยได้
class MySingletonมันควรเป็นfinalอย่างไร?
BearerTokenอินสแตนซ์ไม่คงที่เนื่องจากเป็นส่วนหนึ่งของBearerTokenFactory - ซึ่งมีการกำหนดค่าด้วยเซิร์ฟเวอร์ authoriztion เฉพาะ อาจมีBearerTokenFactoryวัตถุจำนวนมาก- แต่ละคนมี "แคช" ของตัวเองBearerTokenที่มันแจกจนกว่าจะหมดอายุ มีhasExpired()การBeraerTokenเรียกget()วิธีการในวิธีการของโรงงานเพื่อให้แน่ใจว่าจะไม่ส่งโทเค็นที่หมดอายุ หากหมดอายุโทเค็นใหม่จะถูกร้องขอจากเซิร์ฟเวอร์การให้สิทธิ์ ย่อหน้าถัดจากบล็อกรหัสอธิบายสิ่งนี้โดยละเอียดยิ่งขึ้น
นี่คือวิธีการใช้งานง่าย ๆsingleton:
public class Singleton {
// It must be static and final to prevent later modification
private static final Singleton INSTANCE = new Singleton();
/** The constructor must be private to prevent external instantiation */
private Singleton(){}
/** The public static method allowing to get the instance */
public static Singleton getInstance() {
return INSTANCE;
}
}
นี่คือวิธีการสร้างสันหลังยาวของคุณอย่างถูกต้องsingleton:
public class Singleton {
// The constructor must be private to prevent external instantiation
private Singleton(){}
/** The public static method allowing to get the instance */
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
/**
* The static inner class responsible for creating your instance only on demand,
* because the static fields of a class are only initialized when the class
* is explicitly called and a class initialization is synchronized such that only
* one thread can perform it, this rule is also applicable to inner static class
* So here INSTANCE will be created only when SingletonHolder.INSTANCE
* will be called
*/
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
}
getInstance()เริ่มต้นเรียนกรณีที่สองจะยกตัวอย่างเดี่ยวเมื่อโทร แต่แน่นอนถ้าคุณไม่มีวิธีการคงที่อื่น ๆ ในชั้นเรียนของคุณSingletonและคุณเรียกgetInstance()ว่าไม่มีความแตกต่างที่แท้จริง
คุณต้องตรวจสอบสำนวนซ้ำถ้าคุณต้องการโหลดตัวแปรอินสแตนซ์ของคลาสอย่างเกียจคร้าน หากคุณต้องการโหลดตัวแปรแบบคงที่หรือแบบซิงเกิลคุณต้องเริ่มใช้สำนวนความต้องการ
นอกจากนี้หากซิงเกิลโทนต้องเป็น seriliazble ฟิลด์อื่น ๆ ทั้งหมดจะต้องเป็นแบบชั่วคราวและต้องใช้เมธอด readResolve () เพื่อรักษาค่าคงที่ของวัตถุซิงเกิล มิฉะนั้นแต่ละครั้งที่วัตถุถูกดีซีเรียลไลซ์อินสแตนซ์ใหม่ของวัตถุจะถูกสร้างขึ้น สิ่ง readResolve () จะแทนที่วัตถุใหม่อ่านโดย readObject () ซึ่งบังคับให้วัตถุใหม่ที่จะเก็บขยะเนื่องจากไม่มีตัวแปรที่อ้างอิงถึงมัน
public static final INSTANCE == ....
private Object readResolve() {
return INSTANCE; // original singleton instance.
}
วิธีต่างๆในการสร้างวัตถุซิงเกิล:
ตาม Joshua Bloch - Enum จะดีที่สุด
คุณสามารถใช้การตรวจสอบการล็อคซ้ำได้อีกด้วย
แม้แต่คลาสสแตติกด้านในสามารถใช้งานได้
Enum ซิงเกิล
วิธีที่ง่ายที่สุดในการติดตั้งซิงเกิลตันที่ปลอดภัยสำหรับเธรดคือการใช้ Enum
public enum SingletonEnum {
INSTANCE;
public void doSomething(){
System.out.println("This is a singleton");
}
}
รหัสนี้ใช้ได้ตั้งแต่การแนะนำ Enum ใน Java 1.5
ล็อคตรวจสอบสองครั้ง
ถ้าคุณต้องการรหัสเดี่ยว "คลาสสิค" ที่ทำงานในสภาพแวดล้อมแบบมัลติเธรด (เริ่มจาก Java 1.5) คุณควรใช้อันนี้
public class Singleton {
private static volatile Singleton instance = null;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class){
if (instance == null) {
instance = new Singleton();
}
}
}
return instance ;
}
}
สิ่งนี้ไม่ปลอดภัยสำหรับเธรดก่อน 1.5 เนื่องจากการใช้คำสำคัญระเหยแตกต่างกัน
การโหลด Singleton ก่อนกำหนด (ใช้ได้แม้กระทั่ง Java 1.5)
การนำไปใช้งานนี้จะสร้างซิงเกิลตันเมื่อโหลดคลาสและให้ความปลอดภัยของเธรด
public class Singleton {
private static final Singleton instance = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return instance;
}
public void doSomething(){
System.out.println("This is a singleton");
}
}
สำหรับ JSE 5.0 ขึ้นไปใช้วิธี Enum ไม่เช่นนั้นให้ใช้วิธีการจับยึดแบบซิงเกิลตัน ((วิธีการโหลดแบบขี้เกียจที่อธิบายโดย Bill Pugh) วิธีการแก้ปัญหาแบบต่อท้ายยังปลอดภัยสำหรับเธรดโดยไม่ต้องการโครงสร้างภาษาพิเศษ (เช่นระเหยหรือซิงโครไนซ์)
อีกข้อโต้แย้งที่มักใช้กับ Singletons คือปัญหาความสามารถในการทดสอบของพวกเขา ซิงเกิลตันนั้นไม่สามารถล้อเลียนเพื่อการทดสอบได้อย่างง่ายดาย หากสิ่งนี้กลายเป็นปัญหาฉันต้องการทำการแก้ไขเล็กน้อยต่อไปนี้:
public class SingletonImpl {
private static SingletonImpl instance;
public static SingletonImpl getInstance() {
if (instance == null) {
instance = new SingletonImpl();
}
return instance;
}
public static void setInstance(SingletonImpl impl) {
instance = impl;
}
public void a() {
System.out.println("Default Method");
}
}
setInstanceวิธีการเพิ่มเติมอนุญาตให้ตั้งค่าการนำ mockup ไปใช้ของชั้นเดี่ยวในระหว่างการทดสอบ:
public class SingletonMock extends SingletonImpl {
@Override
public void a() {
System.out.println("Mock Method");
}
}
วิธีนี้ยังใช้งานได้กับวิธีการเริ่มต้น:
public class SingletonImpl {
private static final SingletonImpl instance = new SingletonImpl();
private static SingletonImpl alt;
public static void setInstance(SingletonImpl inst) {
alt = inst;
}
public static SingletonImpl getInstance() {
if (alt != null) {
return alt;
}
return instance;
}
public void a() {
System.out.println("Default Method");
}
}
public class SingletonMock extends SingletonImpl {
@Override
public void a() {
System.out.println("Mock Method");
}
}
นี่เป็นข้อเสียเปรียบในการเปิดเผยการทำงานนี้กับแอปพลิเคชันทั่วไปด้วย นักพัฒนาคนอื่นที่ทำงานกับโค้ดนั้นอาจถูกล่อลวงให้ใช้วิธี´setInstance เพื่อแก้ไขฟังก์ชั่นที่เฉพาะเจาะจงและทำให้การเปลี่ยนแปลงพฤติกรรมของแอปพลิเคชั่นทั้งหมดดังนั้นวิธีการนี้ควรมีคำเตือนที่ดีอย่างน้อยใน javadoc
ยังคงสำหรับความเป็นไปได้ของการทดสอบการจำลอง (เมื่อจำเป็น) การเปิดเผยโค้ดนี้อาจเป็นราคาที่ยอมรับได้
คลาสซิงเกิลที่ง่ายที่สุด
public class Singleton {
private static Singleton singleInstance = new Singleton();
private Singleton() {}
public static Singleton getSingleInstance() {
return singleInstance;
}
}
ฉันยังคงคิดว่าหลังจาก java 1.5, enum เป็นการนำ singleton ที่ดีที่สุดมาใช้ได้เพราะมันยังช่วยให้มั่นใจได้ว่าแม้ในสภาพแวดล้อมแบบมัลติเธรด - สร้างอินสแตนซ์เดียวเท่านั้น
public enum Singleton{
INSTANCE;
}
และคุณทำ !!!
ลองดูที่โพสต์นี้
ตัวอย่างของรูปแบบการออกแบบ GoF ในไลบรารีหลักของ Java
จากคำตอบที่ดีที่สุดของส่วน "ซิงเกิล"
ซิงเกิล (รู้จักได้โดยวิธีการสร้างที่ส่งคืนอินสแตนซ์เดียวกัน (โดยปกติจะเป็นของตัวเอง) ทุกครั้ง)
- java.lang.Runtime # getRuntime ()
- java.awt.Desktop # getDesktop ()
- java.lang.System # getSecurityManager ()
คุณยังสามารถเรียนรู้ตัวอย่างของ Singleton จากคลาสเนทีฟ Java ด้วยตนเอง
รูปแบบซิงเกิลที่ดีที่สุดที่ฉันเคยเห็นใช้อินเตอร์เฟสผู้จัดหา
ดูด้านล่าง:
public class Singleton<T> implements Supplier<T> {
private boolean initialized;
private Supplier<T> singletonSupplier;
public Singleton(T singletonValue) {
this.singletonSupplier = () -> singletonValue;
}
public Singleton(Supplier<T> supplier) {
this.singletonSupplier = () -> {
// The initial supplier is temporary; it will be replaced after initialization
synchronized (supplier) {
if (!initialized) {
T singletonValue = supplier.get();
// Now that the singleton value has been initialized,
// replace the blocking supplier with a non-blocking supplier
singletonSupplier = () -> singletonValue;
initialized = true;
}
return singletonSupplier.get();
}
};
}
@Override
public T get() {
return singletonSupplier.get();
}
}
บางครั้ง "" อย่างง่ายstatic Foo foo = new Foo();อาจไม่เพียงพอ แค่คิดถึงการแทรกข้อมูลพื้นฐานที่คุณต้องการ
ในทางกลับกันคุณจะต้องซิงโครไนซ์วิธีการใด ๆ ที่ทำให้ตัวแปรซิงเกิลเป็นอินสแตนซ์ การซิงโครไนซ์ไม่เลวเช่นนี้ แต่อาจนำไปสู่ปัญหาด้านประสิทธิภาพหรือการล็อก (ในสถานการณ์ที่ไม่ค่อยพบโดยใช้ตัวอย่างนี้วิธีแก้ปัญหาคือ
public class Singleton {
private static Singleton instance = null;
static {
instance = new Singleton();
// do some of your instantiation stuff here
}
private Singleton() {
if(instance!=null) {
throw new ErrorYouWant("Singleton double-instantiation, should never happen!");
}
}
public static getSingleton() {
return instance;
}
}
จะเกิดอะไรขึ้น คลาสถูกโหลดผ่านตัวโหลดคลาส โดยตรงจากคลาสที่ถูกตีความจาก byte Array, VM เรียกใช้งานสแตติก {} - block นั่นเป็นความลับทั้งหมด: บล็อกแบบคงที่จะถูกเรียกเพียงครั้งเดียวเวลาที่คลาส (ชื่อ) ที่กำหนดของแพ็กเกจที่กำหนดถูกโหลดโดยตัวโหลดคลาสตัวนี้
public class Singleton {
private static final Singleton INSTANCE = new Singleton();
private Singleton(){
if (INSTANCE != null)
throw new IllegalStateException (“Already instantiated...”);
}
public synchronized static Singleton getInstance() {
return INSTANCE;
}
}
เนื่องจากเราได้เพิ่มคำหลักที่ซิงโครไนซ์ก่อน getInstance เราได้หลีกเลี่ยงสภาพการแข่งขันในกรณีที่เธรดสองเธรดเรียกใช้ getInstance ในเวลาเดียวกัน