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

Mat*_*ley 8 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; 
    }
}
Run Code Online (Sandbox Code Playgroud)

我希望很容易看出发生了什么.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();
Run Code Online (Sandbox Code Playgroud)

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

ass*_*ias 1

难道线程1中value的最后一个值还可以是“一”吗?

是的。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();
Run Code Online (Sandbox Code Playgroud)

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