วิธีที่มีประสิทธิภาพในการใช้รูปแบบซิงเกิลใน 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 ได้คงที่ของserialVersionUID
0L
การทำเอกสารฟิลด์และข้อมูลที่เรียงลำดับได้สำหรับประเภท 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 ในเวลาเดียวกัน