2013-08-12 11 views
8

Tôi đã nhìn thấy loại mã này rất nhiều trong các dự án, trong đó ứng dụng muốn có một trình giữ dữ liệu toàn cầu, vì vậy chúng sử dụng một singleton tĩnh mà bất kỳ luồng nào cũng có thể truy cập.Khả năng hiển thị bộ nhớ của các biến được truy cập trong các trình đơn tĩnh trong Java là gì?

public class GlobalData { 

    // Data-related code. This could be anything; I've used a simple String. 
    // 
    private String someData; 
    public String getData() { return someData; } 
    public void setData(String data) { someData = data; } 

    // Singleton code 
    // 
    private static GlobalData INSTANCE; 
    private GlobalData() {} 
    public synchronized GlobalData getInstance() { 
     if (INSTANCE == null) INSTANCE = new GlobalData(); 
     return INSTANCE; 
    } 
} 

Tôi hy vọng thật dễ dàng để xem điều gì đang xảy ra. Người ta có thể gọi GlobalData.getInstance().getData() bất cứ lúc nào trên bất kỳ chủ đề nào. Nếu hai chủ đề gọi setData() với các giá trị khác nhau, ngay cả khi bạn không thể đảm bảo cái nào "thắng", tôi không lo lắng về điều đó.

Nhưng an toàn chỉ là mối quan tâm của tôi ở đây. Điều tôi lo lắng là khả năng hiển thị bộ nhớ. Bất cứ khi nào có một rào cản bộ nhớ trong Java, bộ nhớ đệm được đồng bộ giữa các chủ đề tương ứng. Một rào cản bộ nhớ xảy ra khi đi qua đồng bộ, truy cập vào các biến dễ bay hơi vv

Hãy tưởng tượng kịch bản sau đây xảy ra trong thứ tự thời gian:

// Thread 1 
GlobalData d = GlobalData.getInstance(); 
d.setData("one"); 

// Thread 2 
GlobalData d = GlobalData.getInstance(); 
d.setData("two"); 

// Thread 1 
String value = d.getData(); 

Không phải là nó có thể là giá trị cuối cùng của value trong chủ đề 1 thể vẫn là "one"? Lý do là, thread 2 không bao giờ được gọi là bất kỳ phương pháp đồng bộ sau khi gọi d.setData("two") do đó, không bao giờ có một rào cản bộ nhớ? Lưu ý rằng rào cản bộ nhớ trong trường hợp này xảy ra mỗi khi getInstance() được gọi vì nó được đồng bộ hóa.

+1

Bạn nói 'Người ta có thể gọi GlobalData.getInstance(). GetData() bất kỳ lúc nào trên bất kỳ chuỗi nào. Đó là thread-an toàn vì vậy tôi không lo lắng về điều đó' nhưng nó thực sự không an toàn thread chính xác vì kịch bản mà bạn mô tả. An toàn chủ đề không chỉ là nhìn thấy một vật thể trong trạng thái không nhất quán. Nó cũng về việc xem dữ liệu cũ. – yair

+0

@yair Có nhưng tôi đã thực sự cố gắng để kiểm tra các khía cạnh bộ nhớ khả năng hiển thị như trái ngược với trạng thái không nhất quán dựa trên thực hiện đồng bộ. –

Trả lời

1

Giá trị cuối cùng của giá trị trong chuỗi 1 có thể vẫn là "một" không?

Vâng. Mô hình bộ nhớ java dựa trên các mối quan hệ xảy ra trước (hb). Trong trường hợp của bạn, bạn chỉ có một lần thoát là getInstance xảy ra trước khi tiếp theo getInstance mục nhập, do từ khóa được đồng bộ hóa.

Vì vậy, nếu chúng ta lấy ví dụ của bạn (giả sử các interleaving chủ đề là theo thứ tự):

// Thread 1 
GlobalData d = GlobalData.getInstance(); //S1 
d.setData("one"); 

// Thread 2 
GlobalData d = GlobalData.getInstance(); //S2 
d.setData("two"); 

// Thread 1 
String value = d.getData(); 

Bạn có S1 hb S2. Nếu bạn gọi là d.getData() từ Thread2 sau S2, bạn sẽ thấy "một". Nhưng lần đọc cuối cùng của d không được đảm bảo để xem "hai".

2

Bạn hoàn toàn chính xác.

Không có đảm bảo nào được viết trong một Thread sẽ hiển thị là một hình thức khác.

Để cung cấp sự bảo đảm này, bạn sẽ cần phải sử dụng các từ khóa volatile:

private volatile String someData; 

Ngẫu nhiên bạn có thể tận dụng classloader Java vào chủ đề cung cấp dịch vụ an toàn lười biếng init của singleton của bạn như tài liệu here. Điều này tránh từ khóa synchronized và do đó giúp bạn tiết kiệm một số khóa.

Cần lưu ý rằng thực tiễn tốt nhất được chấp nhận hiện tại là sử dụng enum để lưu trữ dữ liệu singleton trong Java.

2

Đúng, có thể chuỗi 1 vẫn thấy giá trị là "một" vì không có sự kiện đồng bộ hóa bộ nhớ xảy ra và không xảy ra trước mối quan hệ giữa chuỗi 1 và chuỗi 2 (xem phần 17.4.5 of the JLS).

Nếu someDatavolatile thì chuỗi 1 sẽ thấy giá trị là "hai" (giả sử chuỗi 2 hoàn tất trước khi chuỗi 1 tìm nạp giá trị).

Cuối cùng, và tắt chủ đề, việc triển khai singleton hơi kém lý tưởng vì nó đồng bộ hóa trên mọi quyền truy cập. Nó thường là tốt hơn để sử dụng một enum để thực hiện một singleton hoặc ít nhất, gán dụ trong một initializer tĩnh do đó không có cuộc gọi đến constructor được yêu cầu trong phương thức getInstance.