在Java中静态单例中访问的变量的内存可见性是多少?

时间:2013-08-12 22:10:35

标签: java java-memory-model

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

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()时都会发生内存屏障,因为它已同步。

3 个答案:

答案 0 :(得分:2)

你是完全正确的。

无法保证一个Thread中的写入可见另一个。

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

private volatile String someData;

顺便提一下,您可以利用Java类加载器来提供单例的线程安全延迟初始化,如文档here所示。这样可以避免synchronized关键字,从而为您节省一些锁定。

值得注意的是,目前公认的最佳做法是使用enum在Java中存储单例数据。

答案 1 :(得分:2)

正确,线程1可能仍然将值视为"一个"因为没有发生内存同步事件,所以在线程1和线程2之间的关系之前没有发生(参见17.4.5 of the JLS部分)。

如果someDatavolatile,则广告1会将值视为"两个" (假设线程2在线程1获取值之前完成)。

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

答案 2 :(得分:1)

  

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

是的。 java内存模型基于(hb)关系之前发生的事件。在您的情况下,由于synchronized关键字,您只有 getInstance退出 - 在后续getInstance条目之前。

因此,如果我们采用您的示例(假设线程交错是按此顺序):

// 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。如果你在S2之后从Thread2调用d.getData(),你会看到“一个”。但最后一次阅读d并不能保证看到“两个”。