วิธีที่มีประสิทธิภาพในการใช้รูปแบบซิงเกิลใน Java คืออะไร? [ปิด]


812

วิธีที่มีประสิทธิภาพในการใช้รูปแบบซิงเกิลใน Java คืออะไร?


1
"วิธีที่มีประสิทธิภาพในการใช้รูปแบบซิงเกิลตันใน Java คืออะไร" กรุณากำหนดที่มีประสิทธิภาพ
แมเรียนPaździoch

medium.com/@kevalpatel2106/… . นี่เป็นบทความฉบับสมบูรณ์เกี่ยวกับวิธีการบรรลุความปลอดภัยของเธรดการสะท้อนและการจัดลำดับในรูปแบบซิงเกิล นี่คือแหล่งที่ดีที่จะเข้าใจถึงประโยชน์และข้อ จำกัด ของคลาสซิงเกิล
Keval Patel

ดังที่ Joshua Bloch ชี้ให้เห็นใน Effective Java นั้น enum singleton เป็นวิธีที่ดีที่สุด ที่นี่ฉันได้จัดประเภทการใช้งานต่าง ๆ เป็นสันหลังยาว / กระตือรือร้น ฯลฯ
isaolmez

คำตอบ:


782

ใช้ 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 องค์ประกอบเดียวเป็นวิธีที่ดีที่สุดในการใช้งานซิงเกิลตัน "


203
ฉันคิดว่าคนควรเริ่มมองหา enums เป็นเพียงชั้นเรียนที่มีคุณสมบัติ ถ้าคุณสามารถแสดงรายการอินสแตนซ์ของคลาสของคุณในเวลาคอมไพล์ให้ใช้ enum
Amir Arad

7
โดยส่วนตัวฉันไม่ค่อยพบว่าจำเป็นต้องใช้รูปแบบซิงเกิลโดยตรง บางครั้งฉันใช้การฉีดของสปริงขึ้นกับบริบทแอปพลิเคชันที่มีสิ่งที่มันอ้างถึงเป็นซิงเกิล คลาสยูทิลิตี้ของฉันมักจะมีวิธีการคงที่เท่านั้นและฉันไม่ต้องการอินสแตนซ์ของพวกเขา
สตีเฟ่น Denne

3
สวัสดีทุกคนสามารถบอกฉันได้หรือไม่ว่าซิงเกิลประเภทนี้สามารถล้อเลียนและทดสอบในกรณีทดสอบได้อย่างไร ฉันพยายามที่จะสลับอินสแตนซ์ของซิงเกิลปลอมสำหรับประเภทนี้ แต่ทำไม่ได้
Ashish Sharma

29
ฉันคิดว่ามันสมเหตุสมผล แต่ฉันก็ยังไม่ชอบ คุณจะสร้างซิงเกิลตันที่ขยายชั้นเรียนได้อย่างไร? ถ้าคุณใช้ enum คุณจะทำไม่ได้
chharvey

11
@bvdb: หากคุณต้องการความยืดหยุ่นมากมายคุณได้เมาแล้วโดยการใช้ซิงเกิลตันตั้งแต่แรก ความสามารถในการสร้างอินสแตนซ์อิสระเมื่อคุณต้องการมันค่อนข้างมีค่าในตัวเอง
cHao

233

ขึ้นอยู่กับการใช้งานมีคำตอบ "ถูกต้อง" หลายคำ

ตั้งแต่ 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()นี้จะทำให้แน่ใจว่าจะส่งคืนอินสแตนซ์เดียวเท่านั้นแม้ว่าวัตถุนั้นจะได้รับการทำให้เป็นอนุกรมในการรันโปรแกรมก่อนหน้านี้ของคุณก็ตาม


32
การตรวจสอบการสะท้อนไม่มีประโยชน์ หากรหัสอื่นกำลังใช้การไตร่ตรองเกี่ยวกับเอกชนนั่นคือ Game Over ไม่มีเหตุผลที่จะต้องพยายามทำงานอย่างถูกต้องภายใต้การใช้งานในทางที่ผิดดังกล่าว และถ้าคุณลองมันจะเป็นการ "ป้องกัน" ที่ไม่สมบูรณ์ แต่เป็นรหัสที่สูญเปล่ามากมาย
Wouter Coekaerts

5
> "อันดับแรกคุณต้องการให้คลาสเป็นที่สิ้นสุด" มีคนอธิบายเรื่องนี้ได้ไหม
PlagueHammer

2
การป้องกันการยุบตัวหมดลงอย่างสมบูรณ์ (ฉันคิดว่านี่ถูกกล่าวถึงใน Effective Java 2nd Ed)
Tom Hawtin - tackline

9
-1 นี่ไม่ใช่กรณีที่ง่ายที่สุดอย่างแน่นอนมันถูกวางแผนและซับซ้อนโดยไม่จำเป็น ดูคำตอบของโจนาธานสำหรับวิธีแก้ปัญหาที่ง่ายที่สุดที่จริงเพียงพอใน 99.9% ของทุกกรณี
Michael Borgwardt

2
สิ่งนี้มีประโยชน์เมื่อคุณต้องการรับมรดกจากซูเปอร์คลาส คุณไม่สามารถใช้รูปแบบ enum singleton ในกรณีนี้เนื่องจาก enums ไม่สามารถมีซูเปอร์คลาส (พวกเขาสามารถใช้อินเตอร์เฟสได้) ตัวอย่างเช่น Google Guava ใช้ฟิลด์สุดท้ายแบบคงที่เมื่อรูปแบบ enum ซิงเกิลไม่ใช่ตัวเลือก: code.google.com/p/guava-l ไลบรารี/source/browse/trunk/guava/src/…
Etienne Neveu

139

ข้อจำกัดความรับผิดชอบ:ฉันเพิ่งสรุปคำตอบที่ยอดเยี่ยมทั้งหมดและเขียนไว้ในคำพูดของฉัน


ในขณะที่ใช้งานซิงเกิลตันเรามี 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 มันบนของบล็อก


3
เพียงความกระจ่าง: ซิงเกิลที่นำมาใช้โดยใช้ enum นั้นเริ่มต้นได้อย่างขี้เกียจ รายละเอียดที่นี่: stackoverflow.com/questions/16771373/ …

2
คำตอบที่ดี สิ่งสุดท้ายสิ่งหนึ่งแทนที่วิธีการโคลนเพื่อโยนข้อยกเว้น
riship89

2
@xyz คำอธิบายที่ดีฉันมีความสุขและเรียนรู้ได้ง่ายมากและฉันหวังว่าจะไม่ลืมสิ่งนี้
Premraj

1
หนึ่งในคำตอบที่ดีที่สุดที่ฉันเคยเจอกับ stackoverflow ขอบคุณ!
Shay Tsadok

1
มีเป็นปัญหากับการใช้เป็นอันดับ enums เป็นเดี่ยว: ใด ๆ ค่าข้อมูลสมาชิกจะไม่ต่อเนื่องและดังนั้นจึงไม่ได้คืน ดูserialization วัตถุ Java ข้อมูลจำเพาะรุ่น 6.0 ปัญหาอีกประการหนึ่ง: ไม่มีเวอร์ชัน - ทุกประเภท enum ได้คงที่ของserialVersionUID 0Lปัญหาที่สาม: ไม่มีการกำหนดเอง: เมธอด writeObject, classObject, readObject, readObjectNoData, writeReplace และ readResolve ใด ๆ ที่ระบุเฉพาะจะถูกละเว้นระหว่างการทำให้เป็นอนุกรมและ
Basil Bourque

124

วิธีการแก้ปัญหาที่โพสต์โดย 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;
    }
}

1
ยุติธรรมพอ! ฉันพอใจกับความผันผวนและการใช้งาน อ้อและไชโยสามคนสำหรับ JCiP
Stu Thompson

1
เห็นได้ชัดว่านี่เป็นวิธีการที่ William Pugh สนับสนุนโดยชื่อเสียงของ FindBugz
Stu Thompson

4
@Stu รุ่นแรกของ Effective Java (ลิขสิทธิ์ 2001) ให้รายละเอียดเกี่ยวกับรูปแบบนี้ภายใต้หัวข้อที่ 48
Pascal Thivent

9
@ ไม่: สิ่งที่เกี่ยวกับการสร้างคอนสตรัคส่วนตัว?
xyz

2
@ AlikElzin-kilaka ไม่มาก อินสแตนซ์ถูกสร้างขึ้นในขั้นตอนการโหลดคลาสสำหรับ BarHolderซึ่งล่าช้าจนกว่าจะจำเป็นครั้งแรก คอนสตรัคบาร์จะมีความซับซ้อนที่สุดเท่าที่คุณต้องการ getBar()แต่ก็จะไม่ได้รับการเรียกจนกว่าแรก (และถ้าgetBarเรียกว่า "เร็วเกินไป" คุณจะพบปัญหาเดียวกันไม่ว่าจะใช้งานแบบ Singleons อย่างไร) คุณสามารถดูการโหลดคลาสที่ขี้เกียจของโค้ดด้านบนได้ที่นี่: pastebin.com/iq2eayiR
Ti Strga

95

หัวข้อความปลอดภัยใน 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) และพิสูจน์ได้ดีขึ้น ไปอ่านและโหวตคำตอบของเขาด้วย


1
ฉันจะเรียนรู้เพิ่มเติมเกี่ยวกับตัวแก้ไขแบบระเหยได้ที่ไหน
eleven81

ดูความคิดเห็นของstackoverflow.com/questions/70689/…
Pascal Thivent

2
ฉันคิดว่ามันสำคัญที่จะพูดถึงการโจมตีด้วยการไตร่ตรอง จริงที่นักพัฒนาส่วนใหญ่ไม่จำเป็นต้องกังวล แต่ดูเหมือนว่าตัวอย่างเช่นเหล่านี้ (มากกว่าซิงเกิลอิง Enum) ควรมีรหัสที่ป้องกันการโจมตีหลายครั้ง
luis.espinal

2
ไม่จำเป็นต้องใช้คำสำคัญแบบระเหยที่นี่เนื่องจากการซิงโครไนซ์จะให้ทั้งการยกเว้นร่วมกันและการมองเห็นหน่วยความจำ
Hemant

2
ทำไมต้องกังวลกับสิ่งเหล่านี้ใน Java 5+? ความเข้าใจของฉันคือว่าวิธีการ enum ให้ทั้งความปลอดภัยของด้ายและการเริ่มต้นขี้เกียจ มันง่ายกว่ามาก ... นอกจากนี้หากคุณต้องการหลีกเลี่ยงการ enum ฉันจะยังคงป้องกันวิธีการเรียนแบบคงที่ซ้อนกัน ...
Alexandros

91

ลืมการเริ่มต้นขี้เกียจมันมีปัญหาเกินไป นี่คือทางออกที่ง่ายที่สุด:

public class A {    

    private static final A INSTANCE = new A();

    private A() {}

    public static A getInstance() {
        return INSTANCE;
    }
}

21
ตัวแปรอินสแตนซ์ซิงเกิลสามารถทำสุดท้ายได้เช่นกัน เช่นส่วนตัวคงที่สุดท้าย A singleton = new A ();
jatanp

19
นั่นคือการเริ่มต้นขี้เกียจอย่างมีประสิทธิภาพเนื่องจากสแตติกตันจะไม่ถูกสร้างอินสแตนซ์จนกว่าคลาสจะถูกโหลดและคลาสจะไม่ถูกโหลดจนกว่าจะจำเป็น (ซึ่งจะถูกต้องเกี่ยวกับเวลาที่คุณอ้างอิงเมธอด getInstance () เป็นครั้งแรก
Dan Dyer

7
หากคลาส A ทำการโหลดก่อนที่คุณจะต้องการให้สแตติกถูกทำให้เป็นอินสแตนซ์คุณสามารถห่อสแตติกในคลาสภายในแบบสแตติกเพื่อแยกการเริ่มต้นคลาสออก
Tom Hawtin - tackline

3
ฉันเห็นด้วยกับคำตอบนี้ง่ายที่สุดและ Anirudhan ไม่จำเป็นต้องประกาศตัวอย่างสุดท้าย ไม่มีเธรดอื่นจะเข้าถึงคลาสในขณะที่สมาชิกแบบสแตติกถูกเตรียมใช้งาน สิ่งนี้รับประกันโดยคอมไพเลอร์หรืออีกนัยหนึ่งการเริ่มต้นสแตติกทั้งหมดนั้นทำในลักษณะที่ซิงโครไนซ์ - เพียงเธรดเดียวเท่านั้น
inor

4
วิธีนี้มีข้อ จำกัด หนึ่งข้อ: ตัวสร้างไม่สามารถทำการยกเว้นได้
wangf

47

ตรวจสอบให้แน่ใจว่าคุณต้องการมันจริงๆ ทำ google สำหรับ "singleton anti-pattern" เพื่อดูข้อโต้แย้งบางอย่าง ไม่มีอะไรผิดปกติกับมันฉันคิดว่า แต่มันเป็นเพียงกลไกสำหรับการเปิดเผยทรัพยากร / ข้อมูลทั่วโลกบางส่วนเพื่อให้แน่ใจว่านี่เป็นวิธีที่ดีที่สุด โดยเฉพาะอย่างยิ่งฉันพบว่าการพึ่งพาการฉีดมีประโยชน์มากขึ้นโดยเฉพาะถ้าคุณใช้การทดสอบหน่วยเพราะ DI ช่วยให้คุณสามารถใช้ทรัพยากรจำลองเพื่อการทดสอบ


คุณสามารถใส่ค่าการเยาะเย้ยด้วยวิธีการดั้งเดิมเช่นกัน แต่ฉันเดาว่ามันไม่ใช่วิธีมาตรฐาน / srping ดังนั้นการทำงานพิเศษที่ได้รับเป็นรหัสดั้งเดิมเท่านั้น ...
tgkprog

21

ฉันประหลาดใจกับคำตอบบางข้อที่แนะนำ DI เป็นทางเลือกในการใช้ซิงเกิล นี่เป็นแนวคิดที่ไม่เกี่ยวข้อง คุณสามารถใช้ DI เพื่อฉีดอินสแตนซ์ทั้งแบบซิงเกิลหรือแบบที่ไม่ใช่ซิงเกิล (เช่นต่อเธรด) อย่างน้อยก็เป็นจริงถ้าคุณใช้ Spring 2.x ฉันไม่สามารถพูดกับ DI อื่น ๆ ได้

ดังนั้นคำตอบของฉันเกี่ยวกับ OP น่าจะเป็น (ในทุกตัวอย่างยกเว้นโค้ดตัวอย่างที่น่าสนใจที่สุด):

  1. ใช้กรอบ DI เช่น Spring แล้ว
  2. ทำให้เป็นส่วนหนึ่งของการกำหนดค่า DI ของคุณไม่ว่าการพึ่งพาของคุณจะเป็นแบบซิงเกิลตันร้องขอการกำหนดขอบเขตกำหนดขอบเขตหรืออะไรก็ตาม

วิธีการนี้ช่วยให้คุณมีสถาปัตยกรรม decoupled ที่ดี (และมีความยืดหยุ่นและสามารถทดสอบได้) โดยที่ไม่ว่าจะใช้ซิงเกิลตันนั้นเป็นรายละเอียดการใช้งานที่สามารถย้อนกลับได้อย่างง่ายดาย (หากมีซิงเกิลที่คุณใช้นั้นเป็น threadsafe


4
อาจเป็นเพราะคนไม่เห็นด้วยกับคุณ ฉันไม่ได้ลงคะแนนให้คุณ แต่ฉันไม่เห็นด้วย: ฉันคิดว่า DI สามารถใช้เพื่อแก้ปัญหาแบบเดียวกับที่มีอยู่ สิ่งนี้มีพื้นฐานอยู่บนการทำความเข้าใจ "ซิงเกิลตัน" เพื่อหมายถึง "วัตถุที่มีอินสแตนซ์เดี่ยวซึ่งเข้าถึงได้โดยตรงโดยใช้ชื่อสากล" แทนที่จะเป็นเพียง "วัตถุที่มีอินสแตนซ์เดียว" ซึ่งอาจจะมีเล่ห์เหลี่ยมเล็กน้อย
Tom Anderson

2
เพื่อขยายที่เล็กน้อยพิจารณาTicketNumbererซึ่งจะต้องมีเช่นเดียวทั่วโลกและสถานที่ที่คุณต้องการเขียนชั้นเรียนซึ่งมีบรรทัดของรหัสTicketIssuer int ticketNumber = ticketNumberer.nextTicketNumber();ในการคิดแบบซิงเกิลดั้งเดิมบรรทัดของโค้ดจะต้องมีลักษณะTicketNumberer ticketNumberer = TicketNumberer.INSTANCE;ดังนี้ ใน DI public TicketIssuer(TicketNumberer ticketNumberer) { this.ticketNumberer = ticketNumberer; }คิดชั้นเรียนจะมีคอนสตรัคเหมือน
Tom Anderson

2
และมันจะกลายเป็นปัญหาของคนอื่นที่จะเรียกคอนสตรัคเตอร์นั้น กรอบ DI จะทำกับแผนที่โลกบางประเภท; สถาปัตยกรรม DI handbuilt จะทำเพราะmainวิธีการของแอป(หรือหนึ่งในลูกน้องของมัน) จะสร้างการพึ่งพาและจากนั้นเรียกตัวสร้าง โดยพื้นฐานแล้วการใช้ตัวแปรทั่วโลก (หรือวิธีการทั่วโลก) เป็นเพียงรูปแบบที่เรียบง่ายของรูปแบบบริการระบุตำแหน่งที่หวั่นและสามารถถูกแทนที่ด้วยการฉีดพึ่งพาเช่นเดียวกับการใช้รูปแบบอื่น ๆ
Tom Anderson

@ TomAnderson ฉันสับสนจริงๆว่าทำไมคน 'กลัว' รูปแบบตัวระบุบริการ ฉันคิดว่าในกรณีส่วนใหญ่มันมากเกินไปหรือไม่จำเป็นที่สุด แต่มีกรณีที่ดูเหมือนว่ามีประโยชน์ ด้วยจำนวน params DI ที่น้อยกว่าเป็นที่ต้องการแน่นอน แต่ลองนึกภาพ 20+ การกล่าวว่ารหัสไม่ได้มีโครงสร้างไม่ใช่อาร์กิวเมนต์ที่ถูกต้องเพราะบางครั้งการจัดกลุ่มพารามิเตอร์ก็ไม่สมเหตุสมผล นอกจากนี้จากมุมมองการทดสอบหน่วยฉันไม่สนใจเกี่ยวกับการทดสอบบริการเพียงตรรกะทางธุรกิจของมันและถ้ามันถูกรหัสแล้วมันจะง่าย ฉันเห็นความต้องการนี้ในโครงการขนาดใหญ่มากเท่านั้น
แบรนดอนหลิง

20

พิจารณาเหตุผลที่คุณต้องการซิงเกิลตันก่อนที่จะเขียน มีการถกเถียงกันแบบกึ่งศาสนาเกี่ยวกับการใช้สิ่งเหล่านี้ซึ่งคุณสามารถสะดุดได้ง่ายถ้าคุณใช้ google singletons ใน Java

โดยส่วนตัวแล้วฉันพยายามหลีกเลี่ยงซิงเกิลตันบ่อยที่สุดเท่าที่จะเป็นไปได้ด้วยหลายสาเหตุ ฉันรู้สึกว่าบ่อยครั้งที่ถูกทารุณกรรมเพราะพวกเขาเข้าใจง่ายโดยทุกคนพวกเขาใช้เป็นกลไกในการรับข้อมูล "ทั่วโลก" ในการออกแบบ OO และใช้เพราะมันง่ายต่อการหลีกเลี่ยงการจัดการวงจรชีวิตของวัตถุ (หรือ จริงๆคิดเกี่ยวกับวิธีที่คุณสามารถทำ A จากภายใน B) ดูสิ่งต่าง ๆ เช่น Inversion of Control (IoC) หรือ Dependency Injection (DI) สำหรับพื้นกลางที่สวยงาม

หากคุณต้องการวิกิพีเดียจริง ๆ ก็มีตัวอย่างที่ดีเกี่ยวกับการใช้ซิงเกิลที่เหมาะสม


ตกลง เป็นคลาสพื้นฐานที่เริ่มเตะใบสมัครส่วนที่เหลือของคุณและหากมีการทำซ้ำคุณจะจบลงด้วยความสับสนวุ่นวายที่สมบูรณ์ (เช่นการเข้าถึงทรัพยากรหรือบังคับใช้การรักษาความปลอดภัยเดียว) การส่งผ่านข้อมูลทั่วโลกทั่วทั้งแอปพลิเคชันของคุณคือการเชื่อมต่อสีแดงขนาดใหญ่ ใช้มันเมื่อคุณรับทราบว่าคุณต้องการมันจริงๆ
Salvador Valencia

16

ต่อไปนี้เป็น 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;
    }
}

13

ฉันใช้ Spring Framework เพื่อจัดการซิงเกิลของฉัน มันไม่ได้บังคับใช้ "singleton-ness" ของคลาส (ซึ่งคุณไม่สามารถทำได้จริง ๆ ถ้ามีตัวโหลดคลาสหลายตัวที่เกี่ยวข้อง) แต่มีวิธีง่าย ๆ ในการสร้างและกำหนดค่าโรงงานที่แตกต่างกันสำหรับการสร้างวัตถุประเภทต่างๆ


11

Wikipedia มีตัวอย่างของ singletons เช่นเดียวกับใน Java การปรับใช้ Java 5 ดูค่อนข้างสมบูรณ์และมีความปลอดภัยในการใช้เธรด


11

รุ่น 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;
    }
}

โหลดขี้เกียจด้ายปลอดภัยด้วยการไม่บล็อกประสิทธิภาพสูง


10

หากคุณไม่ต้องการโหลดขี้เกียจก็ลอง

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 หรือสร้างรีจิสตรีสำหรับซิงเกิลทั้งหมดของคุณ


2
รุ่นแรกดีที่สุด สมมติว่าคลาสไม่ทำอะไรเลยนอกจากให้ซิงเกิลตันโดยทั่วไปแล้วมันจะถูกสร้างอินสแตนซ์ที่จุดเดียวกับเวอร์ชันที่สองเนื่องจากการโหลดคลาสที่ขี้เกียจ
Dan Dyer

1
การตรวจสอบซ้ำไม่มีจุดหมายสำหรับไฟฟ้าสถิต และทำไมคุณถึงทำให้วิธีการโคลนนิ่งที่มีการป้องกันเป็นสาธารณะ?
Tom Hawtin - tackline

1
-1 เวอร์ชันของการล็อคที่ตรวจสอบสองครั้งของคุณเสีย
assylias

5
นอกจากนี้คุณต้องสร้างตัวแปรซิงเกิลของคุณด้วยvolatile
MyTitle

รุ่นแรกคือขี้เกียจและด้ายปลอดภัย
Miha_x64

9

ฉันอยากจะพูดว่าเอนุมซิงเกิล

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 ไม่จำเป็นเนื่องจากไม่มีการเปลี่ยนแปลงในประเภทของข้อมูลที่ส่ง

อ้างถึงจากเอกสารของ Oracle

ปัญหาอีกประการหนึ่งของ Singletons ทั่วไปคือเมื่อคุณใช้Serializableส่วนต่อประสานพวกมันจะไม่เหลือ Singleton อีกต่อไปเพราะreadObject()เมธอดจะส่งคืนอินสแตนซ์ใหม่เช่น Constructor ใน Java สิ่งนี้สามารถหลีกเลี่ยงได้โดยใช้readResolve()และละทิ้งอินสแตนซ์ที่สร้างขึ้นใหม่โดยแทนที่ด้วยซิงเกิลตันด้านล่าง

 // readResolve to prevent another instance of Singleton
 private Object readResolve(){
     return INSTANCE;
 }

สิ่งนี้อาจมีความซับซ้อนมากยิ่งขึ้นถ้าคลาส Singleton ของคุณรักษาสถานะไว้ตามที่คุณต้องการเพื่อให้เป็นแบบชั่วคราว แต่ด้วย Enum Singleton การรับประกันการทำให้เป็นอนุกรมโดย JVM


อ่านดี

  1. รูปแบบซิงเกิล
  2. Enums, Singletons และ Deserialization
  3. การล็อคที่ตรวจสอบซ้ำและรูปแบบซิงเกิล

8
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");
    }
}

(1) ไม่กระตือรือร้นมันขี้เกียจเนื่องจากกลไกการโหลดคลาส JVM
Miha_x64

@ Miha_x64 เมื่อไหร่ที่ฉันบอกว่าการโหลดกระตือรือร้นฉันก็บอกว่าการเริ่มต้นกระตือรือร้นถ้าคุณคิดว่าทั้งคู่เหมือนกันแล้วสิ่งที่กำลังโหลดกระตือรือร้นคุณควรจะเขียนหนังสือและแก้ไขข้อผิดพลาดที่ผู้เขียนก่อนหน้านี้เช่น Joshua Bloch
Dheeraj

Java ที่มีประสิทธิภาพเป็นหนังสือที่ยอดเยี่ยม แต่ต้องมีการแก้ไขอย่างแน่นอน
Miha_x64

@ Miha_x64 กำลังโหลดอะไรคุณสามารถอธิบายด้วยตัวอย่าง
Dheeraj

ทำสิ่งที่ 'กระตือรือร้น' หมายถึง 'เร็วที่สุด' ตัวอย่างเช่นไฮเบอร์เนตรองรับการโหลดความสัมพันธ์อย่างกระตือรือร้นหากต้องการอย่างชัดเจน
Miha_x64

8

อาจจะช้าไปหน่อยสำหรับเกมนี้ แต่มีความแตกต่างกันนิดหน่อยในการใช้ซิงเกิล รูปแบบตัวยึดไม่สามารถใช้ได้ในหลาย ๆ สถานการณ์ และ 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 ซิงเกิล คุณสามารถค้นหาข้อมูลเพิ่มเติมเกี่ยวกับตัวอย่างเหล่านี้และตัวอย่างของรูปแบบ "ผู้ถือ" ที่นั่น นอกจากนี้ยังมีตัวอย่างในโลกแห่งความจริงที่แสดงวิธีการระเหยที่ตรวจสอบซ้ำอีกครั้ง หวังว่านี่จะช่วยได้


คุณช่วยอธิบายได้ไหมว่าทำไมBearerToken instanceในบทความของคุณถึงไม่ใช่static? แล้วมันคือresult.hasExpired()อะไร
Woland

แล้วclass MySingletonมันควรเป็นfinalอย่างไร?
Woland

1
@Woland BearerTokenอินสแตนซ์ไม่คงที่เนื่องจากเป็นส่วนหนึ่งของBearerTokenFactory - ซึ่งมีการกำหนดค่าด้วยเซิร์ฟเวอร์ authoriztion เฉพาะ อาจมีBearerTokenFactoryวัตถุจำนวนมาก- แต่ละคนมี "แคช" ของตัวเองBearerTokenที่มันแจกจนกว่าจะหมดอายุ มีhasExpired()การBeraerTokenเรียกget()วิธีการในวิธีการของโรงงานเพื่อให้แน่ใจว่าจะไม่ส่งโทเค็นที่หมดอายุ หากหมดอายุโทเค็นใหม่จะถูกร้องขอจากเซิร์ฟเวอร์การให้สิทธิ์ ย่อหน้าถัดจากบล็อกรหัสอธิบายสิ่งนี้โดยละเอียดยิ่งขึ้น
Michael Andrews

4

นี่คือวิธีการใช้งานง่าย ๆ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();
    }
}

ทั้งคู่ขี้เกียจสมมติว่าสิ่งเดียวที่คุณต้องการจากซิงเกิลตันคืออินสแตนซ์ของมัน
Miha_x64

@ Miha_x64 กรณีแรกจะยกตัวอย่างเดี่ยวเมื่อ JVM getInstance()เริ่มต้นเรียนกรณีที่สองจะยกตัวอย่างเดี่ยวเมื่อโทร แต่แน่นอนถ้าคุณไม่มีวิธีการคงที่อื่น ๆ ในชั้นเรียนของคุณSingletonและคุณเรียกgetInstance()ว่าไม่มีความแตกต่างที่แท้จริง
Nicolas Filotto

3

คุณต้องตรวจสอบสำนวนซ้ำถ้าคุณต้องการโหลดตัวแปรอินสแตนซ์ของคลาสอย่างเกียจคร้าน หากคุณต้องการโหลดตัวแปรแบบคงที่หรือแบบซิงเกิลคุณต้องเริ่มใช้สำนวนความต้องการ

นอกจากนี้หากซิงเกิลโทนต้องเป็น seriliazble ฟิลด์อื่น ๆ ทั้งหมดจะต้องเป็นแบบชั่วคราวและต้องใช้เมธอด readResolve () เพื่อรักษาค่าคงที่ของวัตถุซิงเกิล มิฉะนั้นแต่ละครั้งที่วัตถุถูกดีซีเรียลไลซ์อินสแตนซ์ใหม่ของวัตถุจะถูกสร้างขึ้น สิ่ง readResolve () จะแทนที่วัตถุใหม่อ่านโดย readObject () ซึ่งบังคับให้วัตถุใหม่ที่จะเก็บขยะเนื่องจากไม่มีตัวแปรที่อ้างอิงถึงมัน

public static final INSTANCE == ....
private Object readResolve() {
  return INSTANCE; // original singleton instance.
} 

3

วิธีต่างๆในการสร้างวัตถุซิงเกิล:

  1. ตาม Joshua Bloch - Enum จะดีที่สุด

  2. คุณสามารถใช้การตรวจสอบการล็อคซ้ำได้อีกด้วย

  3. แม้แต่คลาสสแตติกด้านในสามารถใช้งานได้


3

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");
  }

}

2

สำหรับ JSE 5.0 ขึ้นไปใช้วิธี Enum ไม่เช่นนั้นให้ใช้วิธีการจับยึดแบบซิงเกิลตัน ((วิธีการโหลดแบบขี้เกียจที่อธิบายโดย Bill Pugh) วิธีการแก้ปัญหาแบบต่อท้ายยังปลอดภัยสำหรับเธรดโดยไม่ต้องการโครงสร้างภาษาพิเศษ (เช่นระเหยหรือซิงโครไนซ์)


2

อีกข้อโต้แย้งที่มักใช้กับ 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

ยังคงสำหรับความเป็นไปได้ของการทดสอบการจำลอง (เมื่อจำเป็น) การเปิดเผยโค้ดนี้อาจเป็นราคาที่ยอมรับได้


1

คลาสซิงเกิลที่ง่ายที่สุด

public class Singleton {
  private static Singleton singleInstance = new Singleton();
  private Singleton() {}
  public static Singleton getSingleInstance() {
    return singleInstance;
  }
}

1
นี้เป็นเช่นเดียวกับคำตอบของโจนาธานด้านล่าง
Inor

1
ซ้ำของคำตอบนี้พี่น้องโดยโจนาธานโพสต์เมื่อห้าปีก่อน ดูคำตอบสำหรับความคิดเห็นที่น่าสนใจ
Basil Bourque

0

ฉันยังคงคิดว่าหลังจาก java 1.5, enum เป็นการนำ singleton ที่ดีที่สุดมาใช้ได้เพราะมันยังช่วยให้มั่นใจได้ว่าแม้ในสภาพแวดล้อมแบบมัลติเธรด - สร้างอินสแตนซ์เดียวเท่านั้น

public enum Singleton{ INSTANCE; }

และคุณทำ !!!


1
เรื่องนี้ถูกกล่าวถึงแล้วในคำตอบอื่น ๆ ปีที่ผ่านมา
ปาง

0

ลองดูที่โพสต์นี้

ตัวอย่างของรูปแบบการออกแบบ GoF ในไลบรารีหลักของ Java

จากคำตอบที่ดีที่สุดของส่วน "ซิงเกิล"

ซิงเกิล (รู้จักได้โดยวิธีการสร้างที่ส่งคืนอินสแตนซ์เดียวกัน (โดยปกติจะเป็นของตัวเอง) ทุกครั้ง)

  • java.lang.Runtime # getRuntime ()
  • java.awt.Desktop # getDesktop ()
  • java.lang.System # getSecurityManager ()

คุณยังสามารถเรียนรู้ตัวอย่างของ Singleton จากคลาสเนทีฟ Java ด้วยตนเอง


0

รูปแบบซิงเกิลที่ดีที่สุดที่ฉันเคยเห็นใช้อินเตอร์เฟสผู้จัดหา

  • มันเป็นเรื่องทั่วไปและนำมาใช้ใหม่
  • รองรับการเริ่มต้นขี้เกียจ
  • มันจะซิงโครไนซ์จนกว่าจะได้รับการเตรียมใช้งานแล้วผู้จัดจำหน่ายที่ถูกบล็อกจะถูกแทนที่ด้วยผู้ให้บริการที่ไม่ได้ปิดกั้น

ดูด้านล่าง:

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();
    }
}

-3

บางครั้ง "" อย่างง่าย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 นั่นเป็นความลับทั้งหมด: บล็อกแบบคงที่จะถูกเรียกเพียงครั้งเดียวเวลาที่คลาส (ชื่อ) ที่กำหนดของแพ็กเกจที่กำหนดถูกโหลดโดยตัวโหลดคลาสตัวนี้


3
ไม่จริง. ตัวแปรสแตติกถูกเตรียมใช้งานพร้อมกับบล็อกแบบสแตติกเมื่อโหลดคลาส ไม่จำเป็นต้องแยกการประกาศ
Craig P. Motlin

-5
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 ในเวลาเดียวกัน

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.