How to share a volatile field with another thread

63 Views Asked by At
private static boolean stop = false;
private static volatile boolean stop_volatile = false;
public static void main(String[] args) {
    Thread thread0 = new Thread(() -> {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        stop = true;
        stop_volatile = true;
    });

    Thread thread1 = new Thread(() -> {
        while (true) {
            if (stop_volatile){
                System.out.println("thread1:" + stop);
                break;
            }
        }
    });

    Thread thread2 = new Thread(() -> {
        while (true) {
          if (stop){
              System.out.println("thread2:" + stop);
              break;
          }
        }
    });

    thread0.start();
    thread1.start();
    thread2.start();
}

There are two static fields, stop and stop_volatile, and stop_volatile is marked volatile. thread0 changes both to the true. thread1 can stop and thread2 runs forever.

Why does thread1 print "thread1:true", why can it get right value from field stop in thread1 always, it is not a volatile field?

In Thread 0, after modifying the value of "stop_volatile," it writes its working cache to memory and notifies other threads that their working cache of the changed value is invalidated. Then, when other threads access this variable, they need to reload it from memory. Here, "stop" is a non-volatile variable. Why does Thread 1 not continue to use the cached value and instead reloads it as well?

1

There are 1 best solutions below

0
Mark Rotteveel On

The read of a volatile field will make the thread see all changes that occurred before the last write to that field (the so called happens-before relationship). So, given thread1 repeatedly reads the volatile stop_volatile, and the write to stop occurred before the write to stop_volatile, thread1 is guaranteed to see the changes to stop as well.

And as Joachim Sauer points out in the comments, this is the one and only time thread1 accesses stop, so there was nothing "cached" previously for that thread, while thread2 continuously accesses stop, so it is more likely to see a "cached" value (or it might even be optimized to if (false)!).

Though it is tough to read, you may want to read the 17.4 Memory Model section of the Java Language Specification, which details this.

What basically happens is that you have the following actions:

  • in thread0: stop = true happens-before stop_volatile = true
    • stop_volatile = true is a synchronization action
  • in thread1, stop_volatile is read
    • reading from stop_volatile is also a synchronization action
    • as result, the write to stop_volatile happens-before the read to stop_volatile

As specified in section 17.4.5 Happens-before Order:

If we have two actions x and y, we write hb(x, y) to indicate that x happens-before y.

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

  • There is a happens-before edge from the end of a constructor of an object to the start of a finalizer (§12.6) for that object.

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

  • If hb(x, y) and hb(y, z), then hb(x, z).

In other words (using t0 and t1 as shorthand for thread0 and thread1), given:

  • hb(write to stop (t0), write to stop_volatile (t0)), and
  • hb(write to stop_volatile (t0), read from stop_volatile (t1)), also means
  • hb(write to stop (t0), read from stop_volatile (t1)),

And so the subsequent read on thread1 will see the updated value of stop.

You can see this by working out the happens-before further: hb(read from stop_volatile (t1), read from stop (t1)) implies hb(write to stop (t0), read from stop (t1)).