简体   繁体   中英

Deep understanding of volatile in Java

Does Java allows output 1, 0 ? I've tested it very intensively and I cannot get that output. I get only 1, 1 or 0, 0 or 0, 1 .

public class Main {
    private int x;
    private volatile int g;

    // Executed by thread #1
    public void actor1(){
       x = 1;
       g = 1;
    }

    // Executed by thread #2
    public void actor2(){
       put_on_screen_without_sync(g);
       put_on_screen_without_sync(x);
    }
}

Why?

On my eye it is possible to get 1, 0 . My reasoning. g is volatile so it causes that memory order will be ensured. So, it looks like:

actor1:

(1) store(x, 1)
(2) store(g, 1)
(3) memory_barrier // on x86

and, I see the following situation: reorder store(g, 1) before store(x,1) (memory_barrier is after (2)). Now, run thread #2. So, g = 1, x = 0 . Now, we have expected output. What is incorrect in my reasoning?

Any actions before a volatile write happen before (HB) any subsequent volatile read of the same variable. In your case, the write to x happens before the write to g (due to program order).

So there are only three possibilities:

  • actor2 runs first and x and g are 0 - output is 0,0
  • actor1 runs first and x and g are 1 because of the happens before relationship HB - output is 1,1
  • the methods run concurrently and only x=1 is executed (not g=1 ) and the output could be either 0,1 or 0,0 (no volatile write so no guarantee)

No, this isn't possible. According to the JMM, anything that was visible to thread 1 when it writes to a volatile field becomes visible to thread 2 when it reads that field.

There is another example similar to yours provided here :

class VolatileExample {
  int x = 0;
  volatile boolean v = false;
  public void writer() {
    x = 42;
    v = true;
  }

  public void reader() {
    if (v == true) {
      //uses x - guaranteed to see 42.
    }
  }
}

You will never see 1, 0 , but properly explaining this is not going to be easy, spec wise. First let's get some obvious things out of the door. The specification says :

If x and y are actions of the same thread and x comes before y in program order , then hb(x, y).

This means that on the writing thread side, hb(x, g) and on the reading side hb(g, x) . But this is only so, if you would have to reason about each thread individually , as the chapter about Program order says: :

Among all the inter-thread actions performed by each thread t...

So if you imagine running each thread at a time, then happens-before would be correct for each of them, individually. But you don't. Your actors (I am sure you use jcstress there) run concurrently. So relying on "program order" for reasoning is not enough (neither it is correct).

You need to somehow synchronize these two actions now - the reading and the writing . And here is how the specification says it can be done :

A write to a volatile variable synchronizes-with all subsequent reads of v by any thread (where "subsequent" is defined according to the synchronization order).

And later says:

If an action x synchronizes-with a following action y, then we also have hb(x, y).

If you put all of these together now:

          (hb)              (hb)             (hb)
write(x) ------> write(g) -------> read(g) -------> read(x)

This is also called to "transitively" close program order and synchronizes-with order . Since there is hb on every step, seeing 1, 0 (a racy read), is impossible according to the spec.

No, and in fact this property of volatile is used in classes like ConcurrentHashMap to implement a lock-free happy path, roughly like this:

volatile int locked = 0;
...
void mutate() {
    if (Unsafe.compareAndSwapInt(locked,0,1)) { 
    /*this isn't exactly how you call this method, but the point stands: 
      if we read 0, we atomically replace it with 1 and continue on the happy 
      path */
       //we are happy
       //so we mutate the structure and then
       locked = 0;           
    } else {
       //contended lock, we aren't happy
    }
}

Since writes before a volatile write can't be reordered after the volatile write, and reads after volatile read can't be reordered before the volatile read, code like this indeed works as a "lockless locking".

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