Java Singleton và Đồng bộ hóa


117

Vui lòng làm rõ các truy vấn của tôi liên quan đến Singleton và Đa luồng:

  • Cách tốt nhất để triển khai Singleton trong Java, trong môi trường đa luồng là gì?
  • Điều gì xảy ra khi nhiều chủ đề cố gắng truy cập getInstance() phương thức cùng một lúc?
  • Chúng ta có thể tạo ra singleton getInstance() synchronizedkhông?
  • Đồng bộ hóa có thực sự cần thiết khi sử dụng các lớp Singleton không?

Câu trả lời:


211

Vâng, nó là cần thiết. Có một số phương pháp bạn có thể sử dụng để đạt được sự an toàn của chuỗi với khởi tạo lười biếng:

Đồng bộ hóa hà khắc:

private static YourObject instance;

public static synchronized YourObject getInstance() {
    if (instance == null) {
        instance = new YourObject();
    }
    return instance;
}

Giải pháp này yêu cầu mọi luồng phải được đồng bộ trong khi thực tế chỉ cần một vài luồng đầu tiên.

Kiểm tra kỹ đồng bộ hóa :

private static final Object lock = new Object();
private static volatile YourObject instance;

public static YourObject getInstance() {
    YourObject r = instance;
    if (r == null) {
        synchronized (lock) {    // While we were waiting for the lock, another 
            r = instance;        // thread may have instantiated the object.
            if (r == null) {  
                r = new YourObject();
                instance = r;
            }
        }
    }
    return r;
}

Giải pháp này đảm bảo rằng chỉ một số luồng đầu tiên cố gắng lấy được singleton của bạn phải trải qua quá trình lấy được khóa.

Khởi tạo theo yêu cầu :

private static class InstanceHolder {
    private static final YourObject instance = new YourObject();
}

public static YourObject getInstance() {
    return InstanceHolder.instance;
}

Giải pháp này tận dụng các đảm bảo của mô hình bộ nhớ Java về khởi tạo lớp để đảm bảo an toàn luồng. Mỗi lớp chỉ có thể được tải một lần và nó sẽ chỉ được tải khi cần thiết. Điều đó có nghĩa là lần đầu tiên getInstanceđược gọi, InstanceHoldersẽ được tải và instancesẽ được tạo, và vì điều này được kiểm soát bởi ClassLoaders nên không cần đồng bộ hóa bổ sung.


23
Cảnh báo - hãy cẩn thận với đồng bộ hóa được kiểm tra kỹ. Nó không hoạt động bình thường với các JVM trước Java 5 do "sự cố" với mô hình bộ nhớ.
Stephen C

3
-1 Draconian synchronizationDouble check synchronizationgetInstance () - phương thức phải tĩnh!
Grim

2
@PeterRader Họ không cần phải như vậy static, nhưng nó có thể có ý nghĩa hơn nếu họ có. Đã sửa đổi theo yêu cầu.
Jeffrey

4
Bạn đang triển khai khóa được kiểm tra hai lần không đảm bảo hoạt động. Nó thực sự được giải thích trong bài báo bạn đã trích dẫn cho khóa được kiểm tra hai lần. :) Có một ví dụ trong đó bằng cách sử dụng dễ bay hơi hoạt động đúng cho 1.5 và cao hơn (khóa được kiểm tra hai lần chỉ bị hỏng dưới 1.5). Khởi tạo theo yêu cầu chủ cũng được trích dẫn trong bài viết có lẽ sẽ là một giải pháp đơn giản hơn trong câu trả lời của bạn.
mắc kẹtj

2
@MediumOne AFAIK, rkhông cần tính chính xác. Nó chỉ là một cách tối ưu hóa để tránh truy cập vào trường biến động, vì điều đó đắt hơn nhiều so với truy cập một biến cục bộ.
Jeffrey

69

Mẫu này thực hiện khởi tạo lười biếng an toàn luồng của phiên bản mà không cần đồng bộ hóa rõ ràng!

public class MySingleton {

     private static class Loader {
         static final MySingleton INSTANCE = new MySingleton();
     }

     private MySingleton () {}

     public static MySingleton getInstance() {
         return Loader.INSTANCE;
     }
}

Nó hoạt động vì nó sử dụng trình nạp lớp để thực hiện tất cả quá trình đồng bộ hóa miễn phí cho bạn: Lớp MySingleton.Loaderđược truy cập lần đầu tiên bên trong getInstance()phương thức, vì vậy Loaderlớp sẽ tải khi getInstance()được gọi lần đầu tiên. Hơn nữa, trình nạp lớp đảm bảo rằng tất cả quá trình khởi tạo tĩnh hoàn tất trước khi bạn có quyền truy cập vào lớp - đó là điều mang lại cho bạn sự an toàn về luồng.

Nó giống như phép thuật.

Nó thực sự rất giống với mẫu enum của Jhurtado, nhưng tôi thấy mẫu enum là một sự lạm dụng của khái niệm enum (mặc dù nó hoạt động)


11
Đồng bộ hóa vẫn còn tồn tại, nó chỉ được thực thi bởi JVM thay vì lập trình viên.
Jeffrey

@Jeffrey Tất nhiên là bạn nói đúng - Tôi đã nhập tất cả vào (xem phần chỉnh sửa)
Bohemian

2
Tôi hiểu rằng nó không có gì khác biệt đối với JVM, tôi chỉ nói rằng nó đã tạo ra sự khác biệt đối với tôi trong chừng mực mã tự ghi lại. Tôi chưa bao giờ thấy tất cả các chữ hoa trong Java mà không có từ khóa "cuối cùng" trước đây (hoặc enum), có một chút bất hòa về nhận thức. Đối với một người lập trình Java toàn thời gian, nó có thể sẽ không tạo ra sự khác biệt, nhưng nếu bạn chuyển đổi ngôn ngữ qua lại, nó sẽ giúp bạn rõ ràng. Ditto cho người mới. Mặc dù, tôi chắc chắn rằng một người có thể thích nghi với phong cách này khá nhanh chóng; tất cả mũ có lẽ là đủ. Đừng có ý chọn nit, tôi thích bài viết của bạn.
Ruby

1
Câu trả lời xuất sắc, mặc dù tôi không hiểu được một số phần của nó. Bạn có thể giải thích thêm "Hơn nữa, trình nạp lớp đảm bảo rằng tất cả quá trình khởi tạo tĩnh đã hoàn tất trước khi bạn có quyền truy cập vào lớp - đó là điều mang lại cho bạn sự an toàn về luồng." , điều đó giúp an toàn cho luồng như thế nào, tôi hơi bối rối về điều đó.
gaurav jain

2
@ wz366 thực ra, mặc dù nó không cần thiết, tôi đồng ý vì lý do phong cách (vì nó là cuối cùng hiệu quả vì không có mã nào khác có thể truy cập nó) finalnên được thêm vào. Làm xong.
Bohemian

21

Nếu bạn đang làm việc trên môi trường đa luồng trong Java và cần đảm bảo rằng tất cả các luồng đó đang truy cập vào một thể hiện duy nhất của một lớp, bạn có thể sử dụng Enum. Điều này sẽ có thêm lợi thế là giúp bạn xử lý tuần tự hóa.

public enum Singleton {
    SINGLE;
    public void myMethod(){  
    }
}

và sau đó chỉ cần các chủ đề của bạn sử dụng phiên bản của bạn như:

Singleton.SINGLE.myMethod();

8

Có, bạn cần phải thực hiện getInstance()đồng bộ hóa. Nếu không, có thể phát sinh một tình huống trong đó nhiều phiên bản của lớp có thể được thực hiện.

Hãy xem xét trường hợp bạn có hai luồng gọi getInstance()cùng một lúc. Bây giờ hãy tưởng tượng T1 thực thi vừa qua instance == nullkiểm tra, và sau đó T2 chạy. Tại thời điểm này, cá thể không được tạo hoặc thiết lập, vì vậy T2 sẽ vượt qua kiểm tra và tạo cá thể. Bây giờ hãy tưởng tượng rằng việc thực thi chuyển trở lại T1. Bây giờ singleton đã được tạo, nhưng T1 đã thực hiện kiểm tra! Nó sẽ tiến hành tạo lại đối tượng! Làm cho getInstance()đồng bộ hóa ngăn chặn vấn đề này.

Có một số cách để làm cho các chuỗi đơn an toàn, nhưng làm cho getInstance()đồng bộ hóa có lẽ là cách đơn giản nhất.


Nó sẽ hữu ích bằng cách đặt mã tạo đối tượng trong khối Đồng bộ hóa, thay vì thực hiện đồng bộ hóa toàn bộ phương thức?
RickDavis

@RaoG Không. Bạn muốn cả kiểm tra tạo trong khối đồng bộ hóa. Bạn cần hai hoạt động đó diễn ra cùng nhau mà không bị gián đoạn hoặc tình huống tôi mô tả ở trên có thể xảy ra.
Oleksi

7

Enum singleton

Cách đơn giản nhất để triển khai Singleton an toàn cho 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 lại khóa

Nếu bạn muốn mã một singleton “cổ điển” 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 cái 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 theo chuỗi trước 1.5 vì việc triển khai từ khóa biến động 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 cho 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

Bạn cũng có thể sử dụng khối mã tĩnh để khởi tạo phiên bản khi tải lớp và ngăn chặn các vấn đề đồng bộ hóa luồng.

public class MySingleton {

  private static final MySingleton instance;

  static {
     instance = new MySingleton();
  }

  private MySingleton() {
  }

  public static MySingleton getInstance() {
    return instance;
  }

}

@Vimsha Một vài thứ khác. 1. Bạn nên thực hiện instancecuối cùng 2. Bạn nên tạo getInstance()tĩnh.
John Vint

Bạn sẽ làm gì nếu bạn muốn tạo một chuỗi trong singleton.
Arun George

@ arun-george sử dụng một nhóm luồng, một nhóm luồng đơn nếu cần và bao quanh một thời gian (true) -try-catch-throwable nếu bạn muốn đảm bảo luồng của mình không bao giờ chết, bất kể lỗi gì?
tgkprog

0

Cách tốt nhất để triển khai Singleton trong Java, trong môi trường đa luồng là gì?

Tham khảo bài đăng này để biết cách tốt nhất để triển khai Singleton.

Cách hiệu quả để triển khai một mẫu singleton trong Java là gì?

Điều gì xảy ra khi nhiều luồng cố gắng truy cập phương thức getInstance () cùng một lúc?

Nó phụ thuộc vào cách bạn đã triển khai phương pháp. Nếu bạn sử dụng khóa kép mà không có biến biến động, bạn có thể nhận được đối tượng Singleton được xây dựng một phần.

Tham khảo câu hỏi này để biết thêm chi tiết:

Tại sao dễ bay hơi được sử dụng trong ví dụ này về khóa được kiểm tra hai lần

Chúng ta có thể đồng bộ hóa getInstance () của singleton không?

Đồng bộ hóa có thực sự cần thiết khi sử dụng các lớp Singleton không?

Không bắt buộc nếu bạn triển khai Singleton theo những cách dưới đây

  1. nhập tĩnh
  2. enum
  3. LazyInitalaization với Initialization-on-demand_holder_idiom

Tham khảo câu hỏi này để biết thêm chi tiết

Mẫu thiết kế Java Singleton: Câu hỏi


0
public class Elvis { 
   public static final Elvis INSTANCE = new Elvis();
   private Elvis () {...}
 }

Nguồn: Java hiệu quả -> Mục 2

Nó gợi ý sử dụng nó, nếu bạn chắc chắn rằng lớp đó sẽ luôn là singleton.

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.