class C {
Object o;
public void set(Object o){
if(this.o == null){
this.o = o;
}
}
public Object get(){
return o;
}
}
C c = new C();
C c = new C();
Thread#1
Object o1 = c.get(); // 1
Object o2 = c.get(); // 2
Thread#2
c.set(new Object());
o2 == null && o1 != null
可能吗?
为什么呢?
为了清楚我的意思,我编辑了:
如果我们遇到以下情况怎么办?
C c = new C(); // it is given at beginning
1) Object o2 = c.o; // o2 is null. This operation was **reordered** before O `o1 = c.o. The JVM can do it because JMM allows do it.
2) c.o = new Object()` //Thread #2 was executed
3) O o1 = c.o // o1 is not null while o2 is.
答案 0 :(得分:2)
尽管您有数据竞争,但这是不可能的。
数据竞争是因为o
周围的获取和设置并未同步,这意味着在它们订购之前没有发生。您可以通过将这两种方法都设为synchronized
,或将o
设置为易变,或以其他几种方式来解决这个问题。
如果没有同步,则允许JVM对其他线程看到的事件进行重新排序。从Thread1的角度来看,您有以下事件(为简单起见,内联方法):
c.o = null; // initial value
o1 = c.o;
o2 = c.o;
c.o = new Object(); // from Thread2
幸运的是,有两个限制使这项工作:
c.c = null
发生在所有其他操作之前(请参阅JLS 17.4.5)。o1 = c.o
发生在o2 = c.o
之前。 (Thread2将不必须按顺序查看这些读取...但它根本看不到o1
或o2
,所以那里没有问题。 )第一个意味着我们无法执行c.o = null
操作并在c.c = new Object()
之后对其进行排序。第二个意味着从Thread1的角度来看,你在帖子底部提到的重新排序是不允许的(当然,Thread1是唯一看到o1或o2的东西的线程)。
结合这两个限制,我们可以看到,如果Thread1看到c.o
非空,那么它永远不会再看到它恢复为null。如果o1非空,则必须是o2。
请注意,这是相当善变的。例如,让我们说,而不是仅设置c.o
一次,Thread2将其设置两次:
c.set("one");
c.set("two");
在这种情况下, 可以看到o1
是"两个"虽然o2
是"一个"。那是因为那里的操作是:
c.o = null; // initial value
o1 = c.o;
o2 = c.o;
c.c = "one"; // from Thread2
c.c = "two"; // from Thread2
JVM可以重新排序Thread2中的项目,但只要它们不在c.c = null
之前,它就认为合适。特别是,这是有效的:
c.o = null; // initial value
c.c = "two"; // from Thread2
o1 = c.o;
c.c = "one"; // from Thread2
o2 = c.o;
通过将获取和设置同步到o
来删除数据争用,将解决此问题。