Một cách hiệu quả để thực hiện một mẫu đơn trong Java là gì? [đóng cửa]


812

Một cách hiệu quả để thực hiện một mẫu đơn trong Java là gì?


1
"Cách hiệu quả để triển khai một mẫu đơn trong Java là gì?" hãy xác định hiệu quả.
Mary Paździoch

vừa.com /@kevalpatel2106 / . Đây là bài viết đầy đủ về cách đạt được an toàn luồng, phản xạ và tuần tự hóa trong mẫu singleton. Đây là nguồn tốt để hiểu những lợi ích và hạn chế của lớp singleton.
Keval Patel

Như Joshua Bloch chỉ ra trong Java hiệu quả, enum singleton là cách tốt nhất để đi. Ở đây tôi đã phân loại các triển khai khác nhau là lười biếng / háo hức, v.v.
isaolmez

Câu trả lời:


782

Sử dụng một enum:

public enum Foo {
    INSTANCE;
}

Joshua Bloch đã giải thích cách tiếp cận này trong bài nói chuyện được tải lại Java hiệu quả của mình tại Google I / O 2008: liên kết đến video . Đồng thời xem các trang trình bày 30-32 của bản trình bày của anh ấy ( effect_java_reloaded.pdf ):

Cách đúng đắn để thực hiện một Singleton nối tiếp

public enum Elvis {
    INSTANCE;
    private final String[] favoriteSongs =
        { "Hound Dog", "Heartbreak Hotel" };
    public void printFavorites() {
        System.out.println(Arrays.toString(favoriteSongs));
    }
}

Chỉnh sửa: Một phần trực tuyến của "Java hiệu quả" cho biết:

"Cách tiếp cận này có chức năng tương đương với cách tiếp cận lĩnh vực công cộng, ngoại trừ nó ngắn gọn hơn, cung cấp máy móc tuần tự hóa miễn phí, và cung cấp một bảo đảm bằng sắt chống lại nhiều sự khởi tạo, ngay cả khi đối mặt với các cuộc tấn công tuần tự hoặc phản xạ tinh vi. chưa được áp dụng rộng rãi, một loại enum đơn yếu tố là cách tốt nhất để thực hiện một singleton . "


203
Tôi nghĩ mọi người nên bắt đầu xem enums chỉ là một lớp học với một tính năng. nếu bạn có thể liệt kê các thể hiện của lớp của bạn tại thời gian biên dịch, hãy sử dụng một enum.
Amir Arad

7
Cá nhân tôi thường không tìm thấy nhu cầu sử dụng mẫu singleton trực tiếp. Đôi khi tôi sử dụng phép tiêm phụ thuộc của mùa xuân với bối cảnh ứng dụng có chứa những gì nó gọi là singletons. Các lớp tiện ích của tôi có xu hướng chỉ chứa các phương thức tĩnh và tôi không cần bất kỳ phiên bản nào của chúng.
Stephen Denne

3
Xin chào, Ai có thể cho tôi biết làm thế nào loại singleton này có thể được chế giễu và thử nghiệm trong các trường hợp thử nghiệm. Tôi đã cố gắng trao đổi ví dụ singleton giả cho loại này nhưng không thể.
Ashish Sharma

29
Tôi đoán nó có ý nghĩa, nhưng tôi vẫn không thích nó. Làm thế nào bạn sẽ tạo ra một singleton mở rộng một lớp khác? Nếu bạn sử dụng enum, bạn không thể.
chharvey

11
@bvdb: Nếu bạn muốn có nhiều sự linh hoạt, bạn đã bắt đầu bằng cách triển khai một singleton ngay từ đầu. Khả năng tạo một cá thể độc lập khi bạn cần nó là khá vô giá.
cHao

233

Tùy thuộc vào cách sử dụng, có một số câu trả lời "đúng".

Vì java5 cách tốt nhất để làm điều đó là sử dụng enum:

public enum Foo {
   INSTANCE;
}

Trước java5, trường hợp đơn giản nhất là:

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

Chúng ta hãy đi qua mã. Đầu tiên, bạn muốn lớp học là cuối cùng. Trong trường hợp này, tôi đã sử dụng finaltừ khóa để cho người dùng biết từ khóa cuối cùng. Sau đó, bạn cần đặt công cụ xây dựng riêng tư để ngăn người dùng tạo Foo của riêng họ. Việc ném một ngoại lệ từ hàm tạo sẽ ngăn người dùng sử dụng sự phản chiếu để tạo Foo thứ hai. Sau đó, bạn tạo một private static final Footrường để giữ cá thể duy nhất và một public static Foo getInstance()phương thức để trả về nó. Đặc tả Java đảm bảo rằng hàm tạo chỉ được gọi khi lớp được sử dụng lần đầu tiên.

Khi bạn có một đối tượng rất lớn hoặc mã xây dựng nặng VÀ cũng có các phương thức hoặc trường tĩnh có thể truy cập khác có thể được sử dụng trước khi cần một thể hiện, thì và chỉ sau đó bạn cần sử dụng khởi tạo lười biếng.

Bạn có thể sử dụng một private static classđể tải thể hiện. Mã này sẽ trông như sau:

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

Kể từ khi dòng private static final Foo INSTANCE = new Foo(); này chỉ được thực thi khi lớp FooLoader thực sự được sử dụng, nên điều này quan tâm đến việc khởi tạo lười biếng và nó được đảm bảo an toàn cho luồng.

Khi bạn cũng muốn có thể tuần tự hóa đối tượng của mình, bạn cần đảm bảo rằng quá trình khử lưu huỳnh sẽ không tạo ra một bản sao.

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

Phương thức readResolve()sẽ đảm bảo cá thể duy nhất sẽ được trả về, ngay cả khi đối tượng được tuần tự hóa trong lần chạy trước của chương trình của bạn.


32
Việc kiểm tra phản ánh là vô ích. Nếu mã khác đang sử dụng sự phản chiếu trên các trang cá nhân, thì đó là Trò chơi kết thúc. Không có lý do gì để cố gắng hoạt động chính xác dưới sự lạm dụng đó. Và nếu bạn cố gắng, nó sẽ là một "bảo vệ" không hoàn chỉnh, dù sao cũng chỉ là rất nhiều mã bị lãng phí.
Wouter Coekaerts

5
> "Đầu tiên, bạn muốn lớp học là cuối cùng". Ai đó có thể giải thích về điều này xin vui lòng?
Bệnh dịch hạch

2
Việc bảo vệ khử lưu huỳnh bị phá vỡ hoàn toàn (tôi nghĩ điều này được đề cập trong Java Java Ed Ed hiệu quả).
Tom Hawtin - tackline

9
-1 đây hoàn toàn không phải là trường hợp đơn giản nhất, nó phức tạp và phức tạp không cần thiết. Hãy xem câu trả lời của Jonathan cho giải pháp thực sự đơn giản nhất, đủ cho 99,9% trong tất cả các trường hợp.
Michael Borgwardt

2
Điều này rất hữu ích khi singleton của bạn cần kế thừa từ một siêu lớp. Bạn không thể sử dụng mẫu enum singleton trong trường hợp này, vì enums không thể có siêu lớp (mặc dù chúng có thể thực hiện giao diện). Ví dụ: Google Guava sử dụng trường cuối cùng tĩnh khi mẫu enum singleton không phải là một tùy chọn: code.google.com/p/guava-lologists/source/browse/trunk/guava/src/ trộm
Etienne Neveu

139

Tuyên bố miễn trừ trách nhiệm: Tôi vừa tóm tắt tất cả các câu trả lời tuyệt vời và viết nó bằng lời của tôi.


Trong khi triển khai Singleton, chúng tôi có 2 tùy chọn
1. Tải nhanh
2. Tải sớm

Tải lười biếng thêm chi phí bit (rất nhiều thành thật) vì vậy chỉ sử dụng nó khi bạn có một đối tượng rất lớn hoặc mã xây dựng nặng VÀ cũng có các phương thức hoặc trường tĩnh có thể truy cập khác có thể được sử dụng trước khi cần một thể hiện, sau đó và chỉ sau đó bạn cần sử dụng khởi tạo lười biếng. Mặt khác, chọn tải sớm là một lựa chọn tốt.

Cách đơn giản nhất để thực hiện Singleton là

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

Tất cả mọi thứ là tốt ngoại trừ singleton tải sớm của nó. Hãy thử singleton tải lười biếng

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

Cho đến nay rất tốt nhưng anh hùng của chúng ta sẽ không sống sót khi chiến đấu một mình với nhiều chủ đề xấu xa, những người muốn có nhiều ví dụ về anh hùng của chúng ta. Vì vậy, hãy bảo vệ nó khỏi luồng đa luồng xấu xa

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

Nhưng nó không đủ để bảo vệ anh hùng, Thật vậy !!! Đây là điều tốt nhất chúng ta có thể / nên làm để giúp anh hùng của chúng ta

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

Đây được gọi là "Thành ngữ khóa kiểm tra hai lần". Thật dễ dàng để quên đi tuyên bố dễ bay hơi và khó hiểu tại sao nó lại cần thiết.
Để biết chi tiết: http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html

Bây giờ chúng tôi chắc chắn về chủ đề ác nhưng những gì về tuần tự độc ác? Chúng ta phải đảm bảo ngay cả khi khử nối tiếp không có đối tượng mới nào được tạo

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

Phương thức readResolve()sẽ đảm bảo cá thể duy nhất sẽ được trả về, ngay cả khi đối tượng được tuần tự hóa trong lần chạy trước của chương trình của chúng tôi.

Cuối cùng, chúng tôi đã thêm đủ bảo vệ chống lại các luồng và tuần tự hóa nhưng mã của chúng tôi trông cồng kềnh và xấu xí. Hãy cho anh hùng của chúng tôi làm cho hơn

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

Vâng, đây là anh hùng rất giống nhau của chúng tôi :)
Vì dòng private static final Foo INSTANCE = new Foo();chỉ được thực hiện khi lớpFooLoader thực sự được sử dụng, điều này quan tâm đến việc khởi tạo lười biếng,

và nó được đảm bảo là chủ đề an toàn.

Và chúng tôi đã đi rất xa, đây là cách tốt nhất để đạt được mọi thứ chúng tôi đã làm là cách tốt nhất có thể

 public enum Foo {
       INSTANCE;
   }

Mà nội bộ sẽ được đối xử như thế nào

public class Foo {

    // It will be our sole hero
    private static final Foo INSTANCE = new Foo();
}

Đó là nó! Không còn sợ nối tiếp, chủ đề và mã xấu xí. Ngoài ra singleton ENUMS được khởi tạo một cách lười biếng .

Cách tiếp cận này có chức năng tương đương với cách tiếp cận lĩnh vực công cộng, ngoại trừ nó ngắn gọn hơn, cung cấp máy móc tuần tự hóa miễn phí và cung cấp bảo đảm bằng sắt chống lại nhiều lần khởi tạo, ngay cả khi đối mặt với các cuộc tấn công tuần tự hoặc phản xạ tinh vi. Trong khi phương pháp này vẫn chưa được áp dụng rộng rãi, một loại enum đơn yếu tố là cách tốt nhất để thực hiện một singleton.

-Joshua Bloch trong "Java hiệu quả"

Bây giờ bạn có thể đã nhận ra lý do tại sao ENUMS được coi là cách tốt nhất để triển khai Singleton và cảm ơn vì sự kiên nhẫn của bạn :)
Đã cập nhật nó trên blog của tôi .


3
Chỉ cần làm rõ: singletons được thực hiện bằng enum được khởi tạo một cách lười biếng. Chi tiết tại đây: stackoverflow.com/questions/16771373/

2
câu trả lời chính xác. một điều cuối cùng, ghi đè phương thức nhân bản để ném ngoại lệ.
riship89

2
@xyz giải thích hay, tôi thực sự rất thích và học rất dễ dàng và tôi hy vọng không bao giờ quên điều này
Premraj

1
Một trong những câu trả lời hay nhất tôi từng thấy trên stackoverflow. Cảm ơn!
Shay Tsadok

1
một vấn đề serialization với việc sử dụng enums như một singleton: Bất kỳ giá trị trường các thành viên không đăng và do đó không được khôi phục. Xem Đặc tả tuần tự hóa đối tượng Java, phiên bản 6.0 . Một vấn đề khác: Không versioning - tất cả các loại enum có một cố định serialVersionUIDcủa 0L. Vấn đề thứ ba: Không có tùy chỉnh: Bất kỳ phương thức writeObject, readObject, readObjectNoData, writeReplace và readResolve được xác định bởi các loại enum đều bị bỏ qua trong quá trình tuần tự hóa và giải tuần tự hóa.
Basil Bourque

124

Giải pháp được đăng bởi Stu Thompson có giá trị trong Java5.0 trở lên. Nhưng tôi không thích sử dụng nó vì tôi nghĩ nó dễ bị lỗi.

Thật dễ dàng để quên đi tuyên bố dễ bay hơi và khó hiểu tại sao nó lại cần thiết. Nếu không có biến động, mã này sẽ không còn an toàn nữa do các antipotype khóa được kiểm tra hai lần. Xem thêm về điều này trong đoạn 16.2.4 của Java đồng thời trong thực tiễn . Tóm lại: Mẫu này (trước Java5.0 hoặc không có câu lệnh dễ bay hơi) có thể trả về một tham chiếu đến đối tượng Bar (vẫn) ở trạng thái không chính xác.

Mẫu này được phát minh để tối ưu hóa hiệu suất. Nhưng đây thực sự không phải là một mối quan tâm thực sự nữa. Mã khởi tạo lười biếng sau đây rất nhanh và - quan trọng hơn - dễ đọc hơn.

class Bar {
    private static class BarHolder {
        public static Bar bar = new Bar();
    }

    public static Bar getBar() {
        return BarHolder.bar;
    }
}

1
Đủ công bằng! Tôi chỉ thoải mái với sự biến động và nó sử dụng. Oh, và ba người cổ vũ cho JCiP.
Stu Thompson

1
Ồ, đây rõ ràng là cách tiếp cận được ủng hộ bởi William Pugh, của danh tiếng FindBugz.
Stu Thompson

4
@Stu Phiên bản đầu tiên của Java hiệu quả (bản quyền 2001) mô tả chi tiết mẫu này trong mục 48.
Pascal Thivent

9
@Bno: Điều gì về việc làm cho constructor riêng tư?
xyz

2
@ AlikElzin-kilaka Không hoàn toàn. Thể hiện được tạo trong giai đoạn tải lớp cho BarHolder , bị trì hoãn cho đến lần đầu tiên cần thiết. Nhà xây dựng của Bar có thể phức tạp như bạn muốn, nhưng nó sẽ không được gọi cho đến lần đầu tiên getBar(). (Và nếu getBarđược gọi là "quá sớm" thì bạn sẽ phải đối mặt với cùng một vấn đề cho dù việc triển khai đơn lẻ như thế nào.) Bạn có thể thấy lớp lười biếng đang tải mã ở trên: pastebin.com/iq2eayiR
Ti Strga

95

Chủ đề an toàn trong 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;
    }
}

EDIT : Hãy chú ý đến công cụ volatilesửa đổi ở đây. :) Điều quan trọng là vì không có nó, các luồng khác không được đảm bảo bởi JMM (Mô hình bộ nhớ Java) để xem các thay đổi đối với giá trị của nó. Việc đồng bộ hóa không quan tâm đến điều đó - nó chỉ tuần tự hóa quyền truy cập vào khối mã đó.

EDIT 2 : Câu trả lời của @Bno chi tiết cách tiếp cận được đề xuất bởi Bill Pugh (FindBugs) và được cho là tốt hơn. Đi đọc và bỏ phiếu lên câu trả lời của anh ấy quá.


1
Tôi có thể tìm hiểu thêm về công cụ sửa đổi dễ bay hơi ở đâu?
một81

Xem bình luận của stackoverflow.com/questions/70689/
Mạnh

2
Tôi nghĩ điều quan trọng là phải đề cập đến các cuộc tấn công phản chiếu. Đúng là hầu hết các nhà phát triển không cần phải lo lắng, nhưng có vẻ như các ví dụ như thế này (trên các singletons dựa trên Enum) nên bao gồm cả mã bảo vệ chống lại các cuộc tấn công đa thời gian hoặc chỉ đơn giản là từ chối trách nhiệm cho thấy các khả năng đó.
luis.espinal

2
Không cần từ khóa dễ bay hơi ở đây - vì đồng bộ hóa cung cấp cả loại trừ lẫn nhau cộng với khả năng hiển thị bộ nhớ.
Hemant

2
Tại sao phải bận tâm với tất cả điều này trong Java 5+? Hiểu biết của tôi là cách tiếp cận enum cung cấp cả an toàn luồng và khởi tạo lười biếng. Nó cũng đơn giản hơn nhiều ... Hơn nữa, nếu bạn muốn tránh enum, tôi vẫn ngăn cách tiếp cận lớp tĩnh lồng nhau ...
Alexandros

91

Quên khởi tạo lười biếng , nó quá có vấn đề. Đây là giải pháp đơn giản nhất:

public class A {    

    private static final A INSTANCE = new A();

    private A() {}

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

21
biến đối tượng singleton có thể được thực hiện cuối cùng cũng. ví dụ: private static Final A singleton = new A ();
jatanp

19
Đó thực sự là việc khởi tạo lười biếng, vì singleton tĩnh sẽ không được khởi tạo cho đến khi lớp được tải và lớp sẽ không được tải cho đến khi cần (điều này sẽ đúng về thời gian bạn tham chiếu phương thức getInstance () đầu tiên).
Dan Dyer

7
Nếu lớp A được tải theo cách trước khi bạn muốn tĩnh được khởi tạo, bạn có thể bọc tĩnh trong một lớp bên trong tĩnh để tách rời khởi tạo lớp.
Tom Hawtin - tackline

3
Tôi đồng ý câu trả lời này là đơn giản nhất, và Anirudhan, không cần phải khai báo trường hợp cuối cùng. Không có luồng nào khác sẽ có quyền truy cập vào lớp trong khi các thành viên tĩnh được khởi tạo. điều này được đảm bảo bởi trình biên dịch, nói cách khác, tất cả khởi tạo tĩnh được thực hiện theo cách đồng bộ - chỉ một luồng.
vào

4
Cách tiếp cận này có một hạn chế: Hàm tạo không thể ném ngoại lệ.
wangf

47

Hãy chắc chắn rằng bạn thực sự cần nó. Làm một google cho "singleton chống mẫu" để xem một số đối số chống lại nó. Tôi cho rằng không có gì sai với nó nhưng tôi chỉ là một cơ chế để lộ một số tài nguyên / dữ liệu toàn cầu vì vậy hãy chắc chắn rằng đây là cách tốt nhất. Cụ thể, tôi thấy tiêm phụ thuộc hữu ích hơn, đặc biệt nếu bạn cũng đang sử dụng các bài kiểm tra đơn vị vì DI cho phép bạn sử dụng các tài nguyên giả cho mục đích thử nghiệm.


bạn cũng có thể tiêm các giá trị giả bằng phương pháp truyền thống nhưng tôi đoán nó không phải là cách tiêu chuẩn / srping để công việc bổ sung của nó chỉ đạt được mã kế thừa ...
tgkprog

21

Tôi đang bị bối rối bởi một số câu trả lời gợi ý DI thay thế cho việc sử dụng singletons; đây là những khái niệm không liên quan Bạn có thể sử dụng DI để tiêm các trường hợp đơn lẻ hoặc không đơn lẻ (ví dụ: mỗi luồng). Ít nhất điều này là đúng nếu bạn sử dụng Spring 2.x, tôi không thể nói cho các khung DI khác.

Vì vậy, câu trả lời của tôi cho OP sẽ là (trong tất cả trừ mã mẫu tầm thường nhất) thành:

  1. Sử dụng khung DI như Spring, sau đó
  2. Làm cho nó trở thành một phần của cấu hình DI của bạn cho dù phần phụ thuộc của bạn là singletons, yêu cầu trong phạm vi, phạm vi phiên hay bất cứ điều gì.

Cách tiếp cận này cung cấp cho bạn một kiến ​​trúc tách rời (và do đó linh hoạt và có thể kiểm tra) trong đó có nên sử dụng singleton hay không là một chi tiết triển khai có thể đảo ngược dễ dàng (tất nhiên với bất kỳ singletons nào bạn sử dụng đều là chủ đề an toàn).


4
Có lẽ vì mọi người không đồng ý với bạn. Tôi đã không đánh giá cao bạn, nhưng tôi không đồng ý: tôi nghĩ DI có thể được sử dụng để giải quyết các vấn đề tương tự như vậy. Điều này dựa trên việc hiểu "singleton" có nghĩa là "một đối tượng với một thể hiện duy nhất được truy cập trực tiếp bằng tên toàn cầu", chứ không chỉ là "một đối tượng với một thể hiện duy nhất", có lẽ hơi khó hiểu.
Tom Anderson

2
Để mở rộng một chút, hãy xem xét một trường TicketNumbererhợp cần có một thể hiện toàn cục duy nhất và nơi bạn muốn viết một lớp TicketIssuercó chứa một dòng mã int ticketNumber = ticketNumberer.nextTicketNumber();. Trong suy nghĩ đơn lẻ truyền thống, dòng mã trước đó sẽ phải là một cái gì đó như thế TicketNumberer ticketNumberer = TicketNumberer.INSTANCE;. Trong suy nghĩ DI, lớp sẽ có một hàm tạo như thế nào public TicketIssuer(TicketNumberer ticketNumberer) { this.ticketNumberer = ticketNumberer; }.
Tom Anderson

2
Và nó trở thành vấn đề của người khác khi gọi nhà xây dựng đó. Một khung DI sẽ làm điều đó với một bản đồ toàn cầu; một kiến ​​trúc DI được dựng bằng tay sẽ làm điều đó bởi vì mainphương thức của ứng dụng (hoặc một trong số các tay sai của nó) sẽ tạo ra sự phụ thuộc và sau đó gọi hàm tạo. Về cơ bản, việc sử dụng biến toàn cục (hoặc phương thức toàn cầu) chỉ là một hình thức đơn giản của mẫu định vị dịch vụ đáng sợ và có thể được thay thế bằng phép tiêm phụ thuộc, giống như bất kỳ cách sử dụng nào khác của mẫu đó.
Tom Anderson

@TomAnderson Tôi thực sự bối rối về lý do tại sao mọi người 'sợ' mẫu định vị dịch vụ. Tôi nghĩ rằng trong hầu hết các trường hợp, nó quá mức cần thiết hoặc không cần thiết, tuy nhiên, có những trường hợp dường như hữu ích. Với số lượng nhỏ hơn các thông số DI chắc chắn được ưa thích, nhưng hãy tưởng tượng 20+. Nói rằng mã không có cấu trúc không phải là một đối số hợp lệ, bởi vì đôi khi các nhóm tham số chỉ không có ý nghĩa. Ngoài ra, từ góc độ thử nghiệm đơn vị, tôi không quan tâm đến việc thử nghiệm dịch vụ, chỉ là logic kinh doanh của dịch vụ và nếu nó được mã hóa đúng, thì điều này sẽ dễ dàng. Tôi chỉ thấy nhu cầu này trong các dự án quy mô rất lớn.
Brandon Ling

20

Thực sự xem xét lý do tại sao bạn cần một singleton trước khi viết nó. Có một cuộc tranh luận gần như tôn giáo về việc sử dụng chúng mà bạn có thể dễ dàng vấp ngã nếu bạn google singletons trong Java.

Cá nhân tôi cố gắng tránh những người độc thân thường xuyên nhất có thể vì nhiều lý do, một lần nữa hầu hết có thể được tìm thấy bởi những người độc thân. Tôi cảm thấy rằng khá thường xuyên những người độc thân bị lạm dụng bởi vì mọi người đều dễ hiểu, họ được sử dụng như một cơ chế để đưa dữ liệu "toàn cầu" vào thiết kế OO và chúng được sử dụng vì dễ dàng phá vỡ quản lý vòng đời đối tượng (hoặc thực sự suy nghĩ về cách bạn có thể làm A từ bên trong B). Nhìn vào những thứ như Inversion of Control (IoC) hoặc Dependency Injection (DI) để có một khoảng giữa đẹp.

Nếu bạn thực sự cần một cái thì wikipedia có một ví dụ điển hình về việc thực hiện đúng một singleton.


Đã đồng ý. Đây là một lớp nền tảng khởi động phần còn lại của ứng dụng của bạn và nếu nó bị trùng lặp, bạn sẽ kết thúc với một sự hỗn loạn hoàn toàn (tức là truy cập một tài nguyên hoặc thực thi bảo mật). Truyền dữ liệu toàn cầu trên ứng dụng của bạn là một lá cờ đỏ lớn. Sử dụng nó khi bạn thừa nhận bạn thực sự cần nó.
Salvador Valencia

16

Sau đây là 3 cách tiếp cận khác nhau

1)

/**
* Singleton pattern example using Java Enumj
*/
public enum EasySingleton{
    INSTANCE;
}

2) Kiểm tra khóa / tải nhanh

/**
* 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) Phương pháp nhà máy tĩnh

/**
* 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

Tôi sử dụng Spring Framework để quản lý các singletons của mình. Nó không thực thi "tính đơn lẻ" của lớp (dù sao bạn cũng không thể thực hiện được nếu có nhiều trình nạp lớp tham gia) nhưng cung cấp một cách thực sự dễ dàng để xây dựng và định cấu hình các nhà máy khác nhau để tạo các loại đối tượng khác nhau.


11

Wikipedia có một số ví dụ về singletons, cũng bằng Java. Việc triển khai Java 5 trông khá hoàn chỉnh và an toàn theo luồng (áp dụng khóa kiểm tra hai lần).


11

Phiên bản 1:

public class MySingleton {
    private static MySingleton instance = null;
    private MySingleton() {}
    public static synchronized MySingleton getInstance() {
        if(instance == null) {
            instance = new MySingleton();
        }
        return instance;
    }
}

Tải lười biếng, luồng an toàn với chặn, hiệu suất thấp vì synchronized.

Phiên bản 2:

public class MySingleton {
    private MySingleton() {}
    private static class MySingletonHolder {
        public final static MySingleton instance = new MySingleton();
    }
    public static MySingleton getInstance() {
        return MySingletonHolder.instance;
    }
}

Tải chậm, chủ đề an toàn với không chặn, hiệu suất cao.


10

Nếu bạn không cần lười tải thì chỉ cần thử

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

Nếu bạn muốn tải lười biếng và bạn muốn Singleton của mình an toàn cho chuỗi, hãy thử mẫu kiểm tra hai lần

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

Vì mẫu kiểm tra kép không được đảm bảo để hoạt động (do một số vấn đề với trình biên dịch, tôi không biết gì thêm về điều đó.), Bạn cũng có thể thử đồng bộ hóa toàn bộ phương thức getInstance hoặc tạo sổ đăng ký cho tất cả các Singletons của bạn.


2
Phiên bản đầu tiên là tốt nhất. Giả sử lớp không làm gì khác ngoài việc cung cấp một singleton, thì nó thường sẽ được khởi tạo tại cùng một điểm với điểm trong phiên bản thứ hai do tải lớp lười biếng.
Dan Dyer

1
Kiểm tra hai lần là vô nghĩa đối với một tĩnh. Và tại sao bạn đã công khai phương pháp nhân bản được bảo vệ?
Tom Hawtin - tackline

1
-1 phiên bản khóa kiểm tra kép của bạn bị hỏng.
assylias

5
Ngoài ra, bạn cần biến biến số đơn của mìnhvolatile
MyTitle

Phiên bản đầu tiên lười biếng và an toàn chủ đề.
Miha_x64

9

Tôi sẽ nói Enum singleton

Singleton sử dụng enum trong Java nói chung là cách khai báo enum singleton. Enum singleton có thể chứa biến thể hiện và phương thức cá thể. Để đơn giản, cũng lưu ý rằng nếu bạn đang sử dụng bất kỳ phương thức cá thể nào hơn bạn cần đảm bảo an toàn luồng của phương thức đó nếu tất cả đều ảnh hưởng đến trạng thái của đối tượng.

Việc sử dụng một enum rất dễ thực hiện và không có nhược điểm nào liên quan đến các đối tượng tuần tự hóa, chúng phải được phá vỡ theo những cách khác.

/**
* Singleton pattern example using Java Enum
*/
public enum Singleton {
        INSTANCE;
        public void execute (String arg) {
                //perform operation here
        }
}

Bạn có thể truy cập nó bằng cách Singleton.INSTANCE, dễ dàng hơn nhiều so với getInstance()phương thức gọi trên Singleton.

1.12 Nối tiếp các hằng số Enum

Các hằng số Enum được tuần tự hóa khác với các đối tượng tuần tự hóa hoặc ngoại hóa thông thường. Dạng nối tiếp của hằng số enum chỉ bao gồm tên của nó; giá trị trường của hằng số không có trong mẫu. Để tuần tự hóa một hằng số enum, hãy ObjectOutputStreamviết giá trị được trả về bởi phương thức tên của hằng số enum. Để khử lưu lượng hằng số enum, hãy ObjectInputStreamđọc tên hằng từ luồng; hằng số khử lưu lượng thu được bằng cách gọijava.lang.Enum.valueOf phương thức, chuyển loại enum của hằng số cùng với tên hằng nhận được làm đối số. Giống như các đối tượng tuần tự hóa hoặc ngoại hóa khác, hằng số enum có thể hoạt động như các mục tiêu của các tham chiếu trở lại xuất hiện sau đó trong luồng tuần tự hóa.

Quá trình mà hằng enum được đăng không thể được tùy chỉnh: bất kỳ đẳng cấp cụ thể writeObject, readObject, readObjectNoData, writeReplace, và readResolvecác phương pháp xác định bởi loại enum được bỏ qua trong quá trình serialization và deserialization. Tương tự, bất kỳ serialPersistentFieldshoặc serialVersionUIDlĩnh vực khai cũng được bỏ qua - tất cả các loại enum có một cố định serialVersionUIDcủa 0L. Tài liệu các trường và dữ liệu tuần tự hóa cho các loại enum là không cần thiết, vì không có biến thể trong loại dữ liệu được gửi.

Trích dẫn từ tài liệu của Oracle

Một vấn đề khác với Singletons thông thường là một khi bạn triển khai Serializablegiao diện, chúng không còn là Singleton nữa vì readObject()phương thức luôn trả về một thể hiện mới như hàm tạo trong Java. Điều này có thể tránh được bằng cách sử dụng readResolve()và loại bỏ cá thể mới được tạo bằng cách thay thế bằng singleton như bên dưới

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

Điều này có thể trở nên phức tạp hơn nữa nếu Lớp Singleton của bạn duy trì trạng thái, vì bạn cần làm cho chúng tạm thời, nhưng với Enum Singleton, việc tuần tự hóa được đảm bảo bởi JVM.


Đọc tốt

  1. Mẫu đơn
  2. Enums, Singletons và Deserialization
  3. Kiểm tra khóa kép và mẫu Singleton

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) không háo hức, nó lười biếng do cơ chế tải lớp JVM.
Miha_x64

@ Miha_x64 tôi đã nói háo hức khi nào
Dheeraj Sachan

Java hiệu quả là một cuốn sách tuyệt vời, nhưng chắc chắn đòi hỏi phải chỉnh sửa.
Miha_x64

@ Miha_x64 tải háo hức là gì, bạn có thể giải thích bằng ví dụ
Dheeraj Sachan

Làm một cái gì đó "háo hức" có nghĩa là "càng sớm càng tốt". Ví dụ, Hibernate hỗ trợ tải quan hệ một cách háo hức, nếu được yêu cầu rõ ràng.
Miha_x64

8

Có thể hơi muộn với trò chơi này, nhưng có rất nhiều sắc thái xung quanh việc thực hiện một singleton. Mẫu giữ không thể được sử dụng trong nhiều tình huống. Và IMO khi sử dụng một biến động - bạn cũng nên sử dụng một biến cục bộ. Hãy bắt đầu từ đầu và lặp lại vấn đề. Bạn sẽ thấy những gì tôi muốn nói.


Nỗ lực đầu tiên có thể trông giống như thế này:

public class MySingleton {

     private static MySingleton INSTANCE;

     public static MySingleton getInstance() {
        if (INSTANCE == null) {
            INSTANCE = new MySingleton();
        }

        return INSTANCE;
    }
    ...
}

Ở đây chúng ta có lớp MySingleton có một thành viên tĩnh riêng gọi là INSTANCE và một phương thức tĩnh công khai gọi là getInstance (). Lần đầu tiên getInstance () được gọi, thành viên INSTANCE là null. Luồng sau đó sẽ rơi vào điều kiện tạo và tạo một thể hiện mới của lớp MySingleton. Các lệnh gọi tiếp theo tới getInstance () sẽ thấy rằng biến INSTANCE đã được đặt và do đó không tạo ra một phiên bản MySingleton khác. Điều này đảm bảo chỉ có một phiên bản MySingleton được chia sẻ giữa tất cả những người gọi của getInstance ().

Nhưng việc thực hiện này có một vấn đề. Các ứng dụng đa luồng sẽ có một điều kiện chạy đua trong việc tạo ra một thể hiện duy nhất. Nếu nhiều luồng thực thi đạt được phương thức getInstance () tại (hoặc xung quanh) cùng một lúc, thì mỗi luồng sẽ xem thành viên INSTANCE là null. Điều này sẽ dẫn đến mỗi luồng tạo ra một cá thể MySingleton mới và sau đó thiết lập thành viên INSTANCE.


private static MySingleton INSTANCE;

public static synchronized MySingleton getInstance() {
    if (INSTANCE == null) {
        INSTANCE = new MySingleton();
    }

    return INSTANCE;
}

Ở đây, chúng tôi đã sử dụng từ khóa được đồng bộ hóa trong chữ ký phương thức để đồng bộ hóa phương thức getInstance (). Điều này chắc chắn sẽ khắc phục tình trạng cuộc đua của chúng tôi. Chủ đề bây giờ sẽ chặn và nhập phương thức một lần. Nhưng nó cũng tạo ra một vấn đề hiệu suất. Việc triển khai này không chỉ đồng bộ hóa việc tạo một cá thể đơn lẻ, nó còn đồng bộ hóa tất cả các lệnh gọi tới getInstance (), bao gồm cả các lần đọc. Đọc không cần phải được đồng bộ hóa vì chúng chỉ đơn giản trả về giá trị của INSTANCE. Vì các lần đọc sẽ chiếm phần lớn các cuộc gọi của chúng tôi (hãy nhớ rằng, việc khởi tạo chỉ xảy ra trong cuộc gọi đầu tiên), chúng tôi sẽ phải chịu một hiệu suất không cần thiết bằng cách đồng bộ hóa toàn bộ phương thức.


private static MySingleton INSTANCE;

public static MySingleton getInstance() {
    if (INSTANCE == null) {
        synchronize(MySingleton.class) {
            INSTANCE = new MySingleton();
        }
    }

    return INSTANCE;
}

Ở đây, chúng tôi đã chuyển đồng bộ hóa từ chữ ký phương thức, sang một khối được đồng bộ hóa kết thúc việc tạo đối tượng MySingleton. Nhưng điều này có giải quyết được vấn đề của chúng ta không? Chà, chúng tôi không còn chặn đọc nữa, nhưng chúng tôi cũng đã lùi một bước. Nhiều luồng sẽ nhấn phương thức getInstance () cùng một lúc và tất cả chúng sẽ thấy thành viên INSTANCE là null. Sau đó, họ sẽ nhấn khối được đồng bộ hóa nơi người ta sẽ lấy khóa và tạo cá thể. Khi luồng đó thoát khỏi khối, các luồng khác sẽ tranh giành khóa và lần lượt từng luồng sẽ rơi qua khối và tạo một thể hiện mới của lớp chúng ta. Vì vậy, chúng tôi trở lại ngay nơi chúng tôi bắt đầu.


private static MySingleton INSTANCE;

public static MySingleton getInstance() {
    if (INSTANCE == null) {
        synchronized(MySingleton.class) {
            if (INSTANCE == null) {
                INSTANCE = createInstance();
            }
        }
    }

    return INSTANCE;
}

Ở đây chúng tôi phát hành một kiểm tra khác từ bên trong khối. Nếu thành viên INSTANCE đã được đặt, chúng tôi sẽ bỏ qua việc khởi tạo. Điều này được gọi là khóa kiểm tra hai lần.

Điều này giải quyết vấn đề của chúng tôi về nhiều khởi tạo. Nhưng một lần nữa, giải pháp của chúng tôi đã đưa ra một thách thức khác. Các chủ đề khác có thể không thấy được mà thành viên INSTANCE đã được cập nhật. Điều này là do cách Java tối ưu hóa các hoạt động bộ nhớ. Chủ đề sao chép các giá trị ban đầu của các biến từ bộ nhớ chính vào bộ đệm của CPU. Thay đổi giá trị sau đó được ghi vào và đọc từ bộ đệm đó. Đây là một tính năng của Java được thiết kế để tối ưu hóa hiệu suất. Nhưng điều này tạo ra một vấn đề cho việc thực hiện singleton của chúng tôi. Một luồng thứ hai - được xử lý bởi CPU hoặc lõi khác, sử dụng bộ đệm khác - sẽ không thấy các thay đổi được thực hiện bởi lần đầu tiên. Điều này sẽ khiến luồng thứ hai thấy thành viên INSTANCE là null buộc một thể hiện mới của singleton của chúng ta được tạo.


private static volatile MySingleton INSTANCE;

public static MySingleton getInstance() {
    if (INSTANCE == null) {
        synchronized(MySingleton.class) {
            if (INSTANCE == null) {
                INSTANCE = createInstance();
            }
        }
    }

    return INSTANCE;
}

Chúng tôi giải quyết điều này bằng cách sử dụng từ khóa không ổn định trên tờ khai của thành viên INSTANCE. Điều này sẽ báo cho trình biên dịch luôn đọc và ghi vào bộ nhớ chính chứ không phải bộ đệm CPU.

Nhưng sự thay đổi đơn giản này có chi phí. Vì chúng tôi đang bỏ qua bộ đệm CPU, chúng tôi sẽ đạt hiệu suất mỗi lần chúng tôi hoạt động trên thành viên INSTANCE dễ bay hơi - chúng tôi thực hiện 4 lần. Chúng tôi kiểm tra lại sự tồn tại (1 và 2), đặt giá trị (3) và sau đó trả về giá trị (4). Người ta có thể lập luận rằng đường dẫn này là trường hợp rìa vì chúng ta chỉ tạo cá thể trong cuộc gọi đầu tiên của phương thức. Có lẽ một hiệu suất đánh vào sáng tạo là chấp nhận được. Nhưng ngay cả trường hợp sử dụng chính của chúng tôi, đọc, sẽ hoạt động trên thành viên dễ bay hơi hai lần. Một lần để kiểm tra sự tồn tại, và một lần nữa để trả về giá trị của nó.


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

Vì lần truy cập hiệu suất là do hoạt động trực tiếp trên thành viên dễ bay hơi, chúng ta hãy đặt biến cục bộ thành giá trị của biến động và thay vào đó hoạt động trên biến cục bộ. Điều này sẽ làm giảm số lần chúng tôi hoạt động trên biến động, do đó lấy lại một số hiệu suất bị mất của chúng tôi. Lưu ý rằng chúng ta phải đặt lại biến cục bộ của mình khi chúng ta nhập khối được đồng bộ hóa. Điều này đảm bảo nó được cập nhật với bất kỳ thay đổi nào xảy ra trong khi chúng tôi đang chờ khóa.

Tôi đã viết một bài báo về điều này gần đây. Giải mã Singleton . Bạn có thể tìm thêm thông tin về các ví dụ này và một ví dụ về mẫu "người giữ" ở đó. Ngoài ra còn có một ví dụ trong thế giới thực cho thấy cách tiếp cận dễ bay hơi được kiểm tra hai lần. Hi vọng điêu nay co ich.


Bạn có thể vui lòng giải thích tại sao BearerToken instancetrong bài viết của bạn không static? Và nó là result.hasExpired()gì?
Woland

Và những gì về class MySingleton- có lẽ nó nên được final?
Woland

1
@Woland Trường BearerTokenhợp không tĩnh vì nó là một phần của BearerTokenFactory - được cấu hình với một máy chủ ủy quyền cụ thể. Có thể có nhiều BearerTokenFactoryđối tượng - mỗi đối tượng đều có bộ nhớ cache riêng của bộ nhớ cache BearerTokenmà nó đưa ra cho đến khi nó hết hạn. Các hasExpired()phương pháp trên BeraerTokenđược gọi vào những nhà máy get()phương pháp để đảm bảo nó không ra tay một hết hạn thẻ. Nếu hết hạn, mã thông báo mới sẽ được yêu cầu máy chủ ủy quyền qua lại. Đoạn văn sau khối mã giải thích điều này chi tiết hơn.
Michael Andrew

4

Đây là cách thực hiện đơn giản 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;
    }
}

Đây là cách lười biếng đúng cách tạo ra 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();
    }
}

Cả hai đều lười biếng, giả sử điều duy nhất bạn cần từ singleton là ví dụ của nó.
Miha_x64

@ Miha_x64 trường hợp đầu tiên sẽ khởi tạo singleton khi JVM khởi tạo lớp, trường hợp thứ hai sẽ chỉ khởi tạo singleton khi gọi getInstance(). Nhưng thực sự nếu bạn không có bất kỳ phương thức tĩnh nào khác trong lớp Singletonvà bạn chỉ gọi getInstance()thì không có sự khác biệt thực sự.
Nicolas Filotto

3

Bạn cần kiểm tra thành ngữ hai lần nếu bạn cần tải biến thể hiện của một lớp một cách lười biếng. Nếu bạn cần tải một biến tĩnh hoặc một singleton một cách lười biếng, bạn cần khởi tạo theo thành ngữ chủ sở hữu nhu cầu .

Ngoài ra, nếu singleton cần seriliazble, tất cả các trường khác cần phải tạm thời và phương thức readResolve () cần được thực hiện để duy trì bất biến đối tượng singleton. Mặt khác, mỗi khi đối tượng được khử lưu huỳnh, một thể hiện mới của đối tượng sẽ được tạo. Những gì readResolve () làm là thay thế đối tượng mới được đọc bởi readObject (), điều này buộc đối tượng mới đó là rác được thu thập vì không có biến nào đề cập đến nó.

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

3

Nhiều cách khác nhau để tạo đối tượng singleton:

  1. Theo Joshua Bloch - Enum sẽ là tốt nhất.

  2. bạn cũng có thể sử dụng khóa kiểm tra kép.

  3. Ngay cả lớp tĩnh bên trong có thể được sử dụng.


3

Enum đơn

Cách đơn giản nhất để triển khai Singleton an toàn luồng là sử dụng Enum

public enum SingletonEnum {
  INSTANCE;
  public void doSomething(){
    System.out.println("This is a singleton");
  }
}

Mã này hoạt động kể từ khi giới thiệu Enum trong Java 1.5

Kiểm tra khóa kép

Nếu bạn muốn mã hóa một đơn vị cổ điển của người Viking hoạt động trong môi trường đa luồng (bắt đầu từ Java 1.5), bạn nên sử dụng mã này.

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

Điều này không an toàn cho chủ đề trước 1.5 vì việc triển khai từ khóa dễ bay hơi là khác nhau.

Tải sớm Singleton (hoạt động ngay cả trước Java 1.5)

Việc triển khai này khởi tạo singleton khi lớp được tải và cung cấp sự an toàn của luồng.

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

Đối với JSE 5.0 trở lên, hãy sử dụng phương pháp tiếp cận Enum, nếu không, hãy sử dụng phương pháp giữ đơn lẻ tĩnh ((cách tiếp cận tải lười biếng được mô tả bởi Bill Pugh).


2

Một lập luận khác thường được sử dụng chống lại Singletons là các vấn đề về khả năng kiểm tra của họ. Singletons không dễ bị chế giễu cho mục đích thử nghiệm. Nếu điều này trở thành một vấn đề, tôi muốn thực hiện sửa đổi nhỏ sau đây:

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

setInstancePhương thức được thêm vào cho phép thiết lập một triển khai mô phỏng của lớp singleton trong quá trình thử nghiệm:

public class SingletonMock extends SingletonImpl {

    @Override
    public void a() {
        System.out.println("Mock Method");
    }

}

Điều này cũng hoạt động với các phương pháp khởi tạo sớm:

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

}

Điều này cũng có nhược điểm là phơi bày chức năng này cho ứng dụng thông thường. Các nhà phát triển khác làm việc với mã đó có thể được khuyến khích sử dụng phương thức etssetInstance, để thay đổi một chức năng cụ thể và do đó thay đổi toàn bộ hành vi ứng dụng, do đó phương pháp này nên chứa ít nhất một cảnh báo tốt trong javadoc.

Tuy nhiên, đối với khả năng thử nghiệm mockup (khi cần), việc tiếp xúc với mã này có thể là một mức giá chấp nhận được để trả.


1

lớp học đơn giản nhất

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

1
đây giống như câu trả lời của Jonathan bên dưới
inor

1
Bản sao câu trả lời anh chị em này của Jonathan được đăng năm năm trước. Xem câu trả lời cho ý kiến ​​thú vị.
Basil Bourque

0

Tôi vẫn nghĩ sau java 1.5, enum là triển khai singleton tốt nhất hiện có vì nó cũng đảm bảo rằng ngay cả trong môi trường đa luồng - chỉ có một phiên bản được tạo.

public enum Singleton{ INSTANCE; }

và bạn đã hoàn thành !!!


1
Điều này đã được đề cập trong các câu trả lời khác năm trước.
Pang

0

Có một cái nhìn vào bài này.

Ví dụ về các mẫu thiết kế GoF trong các thư viện cốt lõi của Java

Từ phần "Singleton" của câu trả lời hay nhất,

Singleton (có thể nhận ra bằng các phương thức sáng tạo trả về cùng một thể hiện (thường là của chính nó) mọi lúc)

  • java.lang.R.78 # getR.78 ()
  • java.awt.Desktop # getDesktop ()
  • java.lang.System # getSecurityManager ()

Bạn cũng có thể tìm hiểu ví dụ về Singleton từ chính các lớp bản địa Java.


0

Mẫu singleton tốt nhất tôi từng thấy sử dụng giao diện Nhà cung cấp.

  • Nó chung chung và có thể tái sử dụng
  • Nó hỗ trợ khởi tạo lười biếng
  • Nó chỉ được đồng bộ hóa cho đến khi nó được khởi tạo, sau đó nhà cung cấp chặn được thay thế bằng nhà cung cấp không chặn.

Xem bên dưới:

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

Đôi khi một "" đơn giản static Foo foo = new Foo();là không đủ. Chỉ cần nghĩ về một số chèn dữ liệu cơ bản mà bạn muốn làm.

Mặt khác, bạn sẽ phải đồng bộ hóa bất kỳ phương thức nào khởi tạo biến singleton như vậy. Đồng bộ hóa không tệ như vậy, nhưng nó có thể dẫn đến các vấn đề về hiệu năng hoặc khóa (trong các tình huống rất hiếm khi sử dụng ví dụ này. Giải pháp là

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

}

Bây giờ chuyện gì xảy ra? Lớp được tải thông qua trình nạp lớp. Ngay sau khi lớp được diễn giải từ một mảng byte, VM thực thi khối {} - tĩnh . đó là toàn bộ bí mật: Khối tĩnh chỉ được gọi một lần, thời gian lớp (tên) đã cho của gói đã cho được tải bởi trình nạp một lớp này.


3
Không đúng. các biến tĩnh được khởi tạo cùng với các khối tĩnh khi lớp được tải. Không cần phải tách tờ khai.
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;

    }

}

Vì chúng tôi đã thêm từ khóa được Đồng bộ hóa trước getInstance, chúng tôi đã tránh điều kiện cuộc đua trong trường hợp khi hai luồng gọi getInstance cùng một lúc.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.