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.
最佳答案
尽管您存在数据争用,但这是不可能的。
数据竞赛是因为您在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)。从给定线程的角度来看,在该线程上发生的动作始终以它们在代码中出现的顺序发生(也就是JLS 17.4.5)。因此,对于Thread1,
o1 = c.o
发生在o2 = c.o
之前。 (Thread2不必按此顺序查看这些读取内容……但是它根本看不到o1
或o2
,因此在那里没有问题。)首先,这意味着我们无法执行
c.o = null
动作并在c.c = new Object()
之后对其进行排序。第二个意思是,从Thread1的角度来看,不允许您在帖子底部提到的重新排序(当然,Thread1是唯一看到关于o1或o2的内容的唯一线程)。结合这两个限制,我们可以看到,如果Thread1看到
c.o
为非null,那么它将永远不会再看到它又恢复为null。如果o1为非null,则o2也必须为null。请注意,这非常善变。例如,假设Thread2不仅设置了
c.o
一次,还设置了两次: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
来消除数据争用,将解决此问题。关于java - Java内存模型和并发读取,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/45175163/