我在项目中经常看到这种类型的代码,在这些项目中,应用程序需要一个全局数据持有者,因此他们使用任何线程都可以访问的静态单例。
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()
时发生,因为它是同步的。 最佳答案
是的。 Java内存模型基于事前(hb)关系。在您的情况下,由于synced关键字,您只有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的最后读取是“2”。关于java - 在Java中以静态单例访问的变量的内存可见性是什么?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/18197501/