简体   繁体   中英

Java memory model and concurrent read

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());

Is it possible that o2 == null && o1 != null ? Why?


To make it clear what I mean I edited:

What if we have the following situation:

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.

It is not possible, despite the fact that you have a data race.

The data race is because your gets and sets around o aren't synchronized, which means there's no happens-before order with them. You could solve that either by having both methods be synchronized , or by making o volatile, or in a few other ways.

Absent synchronization, the JVM is allowed to reorder events as seen by other threads. From Thread1's perspective, you have the following events (with methods inlined for simplicity):

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

Luckily for you, there are two restrictions that make this work:

  1. The cc = null happens-before all other actions (see JLS 17.4.5 ).
  2. From a given thread's perspective, actions that happen on that thread always happen in the same order they appear in the code (also JLS 17.4.5). So for Thread1, o1 = co happens-before o2 = co . (Thread2 would not have to see those reads in that order... but it never sees o1 or o2 at all, so there's no problem there.)

The first of those means that we can't take the co = null action and order it after cc = new Object() . The second means that the reordering you mention at the bottom of your post is not allowed, from Thread1's perspective (and of course, Thread1 is the only thread that sees anything about o1 or o2).

Combining those two restrictions, we can see that if Thread1 ever sees co be non-null, then it will never see it revert back to null again. If o1 is non-null, then so must o2 be.

Note that this is pretty fickle. For instance, let's say that rather than only setting co once, Thread2 set it twice:

c.set("one");
c.set("two");

In that case, it would be possible to see o1 be "two" while o2 is "one". That's because the operations there are:

c.o = null; // initial value
o1 = c.o;
o2 = c.o;
c.c = "one"; // from Thread2
c.c = "two"; // from Thread2

The JVM can reorder the items from Thread2 however it sees fit, so long as they don't come before that cc = null . In particular, this is valid:

c.o = null; // initial value
c.c = "two"; // from Thread2
o1 = c.o;
c.c = "one"; // from Thread2
o2 = c.o;

Removing the data race, by synchronizing the gets and sets to o , will fix that.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM