There is what java spec says about it:
https://docs.oracle.com/javase/specs/jls/se8/html/jls-17.html#jls-17.4.5
"A write to a volatile field (§8.3.1.4) happens-before every subsequent read of that field."
But what does "subsequent" mean in terms of concurrency? How can we be sure what is subsequent ans what is previous? Let's write a simple test:
public class VolatileTest {
static /*volatile*/ int counter = 0;
static int zeros = 0;
static int ones = 0;
public static void main(String[] args) throws InterruptedException {
int i = 0;
while (i++ < 1_000_000) {
if (i % 100_000 == 0) System.out.println(i);
Thread thread1 = new Thread(VolatileTest::op1);
Thread thread2 = new Thread(VolatileTest::op2);
thread1.start();
thread2.start();
thread1.join();
thread2.join();
counter = 0;
}
System.out.println("Zeros " + zeros);
System.out.println("Ones " + ones);
}
public static void op1() {
counter = 1;
}
public static void op2() {
if (counter == 0) ++zeros;
if (counter == 1) ++ones;
}
}
The final output will be like
Zeros 2095
Ones 997905
And it is predictable.
But when i uncomment the volatile
word in counter
variable, i still get some zeros in the answer, which means that read from volatile count
variable was before the write, despite java spec claims that
write to a volatile field (§8.3.1.4) happens-before every subsequent read of that field.
Where am i wrong?
A write operation to volatile
variable is immediately visible to all the threads. More importantly, any write operation that happens before the volatile write will also become visible. So in the following code:
static volatile int counter = 0;
static int x = 0;
...
// A single thread writes to the counter
x = 1;
counter = 1;
For all other threads that run:
if(counter==1) {
// Here, x = 1 is guaranteed
}
The value of x
is not guaranteed to be 1 if counter
is not volatile. The compiler or the CPU may reorder the memory write operations without the volatile
.