【发布时间】:2013-08-25 06:01:27
【问题描述】:
我在项目中经常看到这种类型的代码,其中应用程序需要一个全局数据持有者,因此他们使用任何线程都可以访问的静态单例。
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() 时都会发生,因为它是同步的。
【问题讨论】:
-
您说
One can call GlobalData.getInstance().getData() at any time on any thread. It's thread-safe so I'm not worried about that,但由于您描述的场景,它实际上并不是线程安全的。线程安全不仅仅是看到处于不一致状态的对象。这也与查看陈旧数据有关。 -
@yair 是的,但我真的试图检查内存可见性方面,而不是基于同步执行的不一致状态。