8

我在项目中经常看到这种类型的代码,其中应用程序需要一个全局数据持有者,因此他们使用任何线程都可以访问的静态单例。

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

我希望很容易看到发生了什么。可以GlobalData.getInstance().getData()随时在任何线程上调用。如果两个线程以不同的值调用 setData(),即使你不能保证哪一个“获胜”,我也不担心。

但是线程安全不是我关心的问题。我担心的是内存可见性。每当Java中存在内存屏障时,缓存的内存就会在相应的线程之间同步。当通过同步、访问 volatile 变量等时会发生内存屏障。

想象一下按时间顺序发生的以下场景:

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

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

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

线程1中的最后一个值是否value仍然可以是"one"?原因是,线程 2 在调用后从未调用任何同步方法,d.setData("two")所以从来没有内存屏障?请注意,在这种情况下,每次getInstance()调用时都会发生内存屏障,因为它是同步的。

4

3 回答 3

3

你是绝对正确的。

不能保证写入一个Thread将是可见的另一个。

要提供此保证,您需要使用以下volatile关键字:

private volatile String someData;

顺便说一句,您可以利用 Java 类加载器来提供单例的线程安全惰性初始化,如此所述。这避免了synchronized关键字,因此为您节省了一些锁定。

值得注意的是,目前公认的最佳实践是enum在 Java 中使用一个来存储单例数据。

于 2013-08-12T22:18:22.107 回答
2

正确,线程 1 可能仍将值视为“一”,因为没有发生内存同步事件并且在线程 1 和线程 2 之间的关系之前没有发生(参见JLS 的第 17.4.5 节)。

如果someData是,volatile那么线程 1 会将值视为“二”(假设线程 2 在线程 1 获取值之前完成)。

最后,题外话,单例的实现稍微不理想,因为它在每次访问时都会同步。通常最好使用枚举来实现单例,或者至少在静态初始化程序中分配实例,因此getInstance方法中不需要调用构造函数。

于 2013-08-12T22:18:28.983 回答
1

线程 1 中 value 的最后一个值是否仍然可以是“一”?

是的。Java 内存模型基于发生在 (hb) 之前的关系。在您的情况下,由于 synchronized 关键字,您只有getInstanceexit 发生在后续getInstanceentry之前。

因此,如果我们以您的示例为例(假设线程交错按该顺序):

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

你有 S1 hb S2。如果您d.getData()在 S2 之后从 Thread2 调用,您会看到“one”。但不能保证最后一次读取 d 会看到“二”。

于 2013-08-12T22:18:19.330 回答