it-swarm-vi.tech

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

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

752

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 Tải lại Java hiệu quả thảo luận 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ả" nói: 

"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ừ việc 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. "

743
Stephen Denne

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 từ khóa final để cho người dùng biết nó là 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 trường private static final Foo để giữ trường hợp duy nhất và phương thức public static Foo getInstance() để trả về trường đó. Đặ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à sau đó bạn chỉ cần sử dụng khởi tạo lười biếng.

Bạn có thể sử dụng 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;
    }
}

Vì dòng private static final Foo INSTANCE = new Foo(); 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 khô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 trường hợp 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.

224
Roel Spilker

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 lười
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. Ngược lại, 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 luồng ác, 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 sự đ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 thi khi lớp FooLoader 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();
}

Đó không còn là nỗi sợ của việc tuần tự hóa, chủ đề và mã xấu xí. Ngoài ra ENUMS singleton đượ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ừ việc nó ngắn gọn hơn, cung cấp các 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 khởi tạo, ngay cả khi đối mặt với tuần tự hóa tinh vi hoặc phản xạ tấn công. 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 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

127
xyz

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 biến động) 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;
    }
}
121
Benno Richters

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

CHỈNH SỬA: Hãy chú ý đến công cụ sửa đổi volatile tại đây. :) Điều này rất quan trọng 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ó. Đồng bộ hóa không đảm nhận việc đó - 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á.

94
Stu Thompson

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

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.

47
Neil Burroughs

Đừng quên Singleton chỉ là một Singleton cho Classloader đã tải nó. Nếu bạn đang sử dụng nhiều bộ tải (Container), mỗi COULD có phiên bản Singleton riêng.

26
Javamann

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ù các 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 Nice (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 là chủ đề an toàn).

21
Andrew Swan

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ì nó dễ phá vỡ đối tượng quản lý vòng đời (hoặc thực sự 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) cho một khu vực 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.

20
Aidos

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;
    }
}
16
Abhijit Gaikwad

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 nếu có nhiều trình nạp lớp) 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.

13
Matt

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 chậm, 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.

11
coderz

Wikipedia có một số ví dụ của 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).

10
macbirdie

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ả Singletons của bạn. 

10
Aleksi Yrttiaho

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 cuộc 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ùng phương thức getInstance () tại (hoặc xung quanh) cùng một lúc, 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 để tạo ra thể hiện 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 việc đọ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 các 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 của 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 CPU 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 năng là do hoạt động trực tiếp trên thành viên dễ bay hơi, hãy để LÊ đặt mộ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 cấu trúc đơn . 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.

8
Michael Andrews

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 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 Singleton.INSTANCE, dễ dàng hơn nhiều so với việc gọi phương thức getInstance() 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, ObjectOutputStream ghi 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, ObjectInputStream đọc tên hằng từ luồng; Sau đó, hằng số khử lưu lượng thu được bằng cách gọi phương thức Java.lang.Enum.valueOf, 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à các hằng số enum được tuần tự hóa không thể được tùy chỉnh: mọi phương thức writeObject, readObject, readObjectNoData, writeReplacereadResolve được xác định bởi các loại enum được bỏ qua trong quá trình tuần tự hóa và giải tuần tự hóa. Tương tự, mọi khai báo trường serialPersistentFields hoặc serialVersionUID cũng bị bỏ qua - tất cả các loại enum đều có serialVersionUID cố định là 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 Oracle

Một vấn đề khác với Singletons thông thường là một khi bạn triển khai giao diện Serializable, chúng không còn là Singleton nữa vì phương thức readObject() luôn trả về một cá thể 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. Khóa được kiểm tra hai lần và mẫu Singleton
8
NullPoiиteя
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");
    }
}
7
Dheeraj Sachan

Đây là cách triển khai singleton đơn giản:

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 singleton của bạn:

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

Bạn cần kiểm tra hai lần thành ngữ 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 trên chủ sở hữu nhu cầu thành ngữ. 

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 () thực hiện 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
Onur

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
Shailendra Singh

Enum singleton

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

Khóa kiểm tra 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 ;
  }
}

Đây không phải là chủ đề an toàn trước 1,5 vì việc thực hiện từ khóa dễ bay hơi là khác nhau.

Tải Singleton sớm (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");
  }

}
3
Dan Moldovan

Đối với JSE 5.0 trở lên, hãy sử dụng phương pháp 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
raoadnan

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

Phương thức setInstance được thêm vào cho phép thiết lập triển khai mockup 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), tiếp xúc với mã này có thể là một mức giá chấp nhận được để trả.

2
user3792852

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
rohan kamat

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 các 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 !!!

0
shikjohari

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 tạo trở lại 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
phi

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