The visibility of variable which write after volatile variable write

Viewed 246
public class Test {
    private static volatile boolean flag = false;
    private static int i = 1;
    public static void main(String[] args) {
        new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            flag = true;
            i += 1;
        }).start();
        new Thread(() -> {
            while (!flag) {
                if (i != 1) {
                    System.out.println(i);
                }
            }
            System.out.println(flag);
            System.out.println(i);
        }).start();
    }
}

Variable i is written after volatile variable flag, but the code output true 2. It seems the modify of i by the first thread is visible to the second thread.

According to my understanding, variable I should be written before flag, then the second thread can be aware of the change.

3 Answers

Accordingly to the language standard (§17.4):

A field may be declared volatile, in which case the Java Memory Model ensures that all threads see a consistent value for the variable

So informally, all threads will have a view of the must update value of that variable.

However, the volatile clause not only implies ensure visibility guarantee of the target variable but also full volatile visibility guarantee, namely:

Actually, the visibility guarantee of Java volatile goes beyond the volatile variable itself. The visibility guarantee is as follows:

If Thread A writes to a volatile variable and Thread B subsequently reads the same volatile variable, then all variables visible to Thread A before writing the volatile variable, will also be visible to Thread B after it has read the volatile variable.

If Thread A reads a volatile variable, then all all variables visible to Thread A when reading the volatile variable will also be re-read from main memory.

According to my understanding, variable I should be written before flag, then the second thread can be aware of the change.

"All variables visible to the Thread A before writing the volatile variable", it does not refer to operation over those variables.

Your code suffers from a data-race.

A data race is when there are 2 memory actions to the same address which are not ordered by a happens before relation, and at least one of these actions is a write.

In this case the write to i is the problem.

The write to i, is after the write to the volatile variable flag and hence there is no happens before relation between writing the i and reading i.

If you would write i before you write to the flag, there would be the following happens before relation:

  1. write of i happens before write of flag due to the program order rule
  2. write of flag happens before read of flag due to volatile variable rule (on the hardware level this is a task for cache coherence).
  3. the read of flag happens before the read of i due to program order rule.

Because the happens before relation is transitive, the write of i happens before the read if i.

So like you already indicated, if you move the write of i in front of the write of the flag; the data race is gone.

The memory model defines guarantees, however anything may happen on top of them.

On x86, all writes have release semantics, and as soon as you write to a variable, its updated value will be visible as soon as possible from other threads.

So the fact that actions before writing to a volatile variable happen-before actions after reading to it, does not prevent actions after writing to it to become visible after reading it.

Related