Java内存模型和并发读取

时间:2017-07-18 19:07:23

标签: java multithreading java-memory-model

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.

1 个答案:

答案 0 :(得分:2)

尽管您有数据竞争,但这是不可能的。

数据竞争是因为o周围的获取和设置并未同步,这意味着在它们订购之前没有发生。您可以通过将这两种方法都设为synchronized,或将o设置为易变,或以其他几种方式来解决这个问题。

如果没有同步,则允许JVM对其他线程看到的事件进行重新排序。从Thread1的角度来看,您有以下事件(为简单起见,内联方法):

c.o = null; // initial value
o1 = c.o;
o2 = c.o;
c.o = new Object(); // from Thread2

幸运的是,有两个限制使这项工作:

  1. c.c = null发生在所有其他操作之前(请参阅JLS 17.4.5)。
  2. 从给定线程的角度来看,在该线程上发生的操作总是以它们在代码中出现的相同顺序发生(也是JLS 17.4.5)。因此,对于Thread1,o1 = c.o发生在o2 = c.o之前。 (Thread2将必须按顺序查看这些读取...但它根本看不到o1o2,所以那里没有问题。 )
  3. 第一个意味着我们无法执行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来删除数据争用,将解决此问题。