See a question on the understanding of the problem of the volatile keyword in Java? "has aroused my interest.
The problem is this:
1 Packagecom.cc.test.volatileTest;2 3 Public classVolatilebarrierexample {4 Private Static BooleanStop =false;5 6 Public Static voidMain (string[] args)throwsinterruptedexception {7Thread thread =NewThread (NewRunnable () {8 @Override9 Public voidrun () {Ten while(!stop) { One } A } - }); - the Thread.Start (); -Thread.Sleep (1000); -Stop =true; - Thread.Join (); + } -}
The main purpose of this code is: the primary thread modifies a global variable of non-volatile type stop, the child thread polls the stop, and if the stop changes, the program exits.
However, if the actual running of this code will cause a dead loop, the program does not exit normally.
Of course we know that because the stop variable is not volatile, the main thread's modification to the stop may not necessarily be caused by the thread's view.
But the master played a pattern, additional defined a static type of volatile variable I, in the while loop for I self-increment operation, the code is as follows:
1 Packagecom.cc.test.volatileTest;2 3 Public classVolatilebarrierexample {4 Private Static BooleanStop =false;5 private static volatile int i = 0;6 7 Public Static voidMain (string[] args)throwsinterruptedexception {8Thread thread =NewThread (NewRunnable () {9 @OverrideTen Public voidrun () { One inti = 0; A while(!stop) { - i++; - } the } - }); - - Thread.Start (); +Thread.Sleep (1000); -Stop =true; + Thread.Join (); A } at}
This program can be completed after a second run, that is, the sub-thread to the volatile type variable I read and write, affecting the non-volatile type variable stop visibility!
It seems confusing, but in fact the problem is not tenable.
Give the general answer first:The visibility of the stop variable is not guaranteed in any of the scenarios. Whether the program can exit normally in these two scenarios is related to the CPU architecture of the JVM implementation and there is no deterministic answer.
The following analysis from two different angles
One: Happens-before principle:
The first scenario is not discussed, even though in the second scenario, although the sub-thread has read-write + non-volatile type variable stop reading for the volatile type variable i, there is only a write to the non-volatile type variable stop in the main thread and therefore cannot be established (The main thread writes to stop) Happens-before the relationship of (the child thread to the stop read) .
That is, you cannot expect the main thread to write to stop to be able to see the quilt thread.
Although the scenario two in the actual operation of the program is still properly terminated, but this is only a good luck, if a JVM implementation or a different CPU architecture, scenario two may also fall into a dead loop.
You can imagine a scenario where the master/child threads are running on Core1/core2, the Core1 cache has a stop copy, and the Core2 cache has a copy of stop and I, and stop and I are not in the same cacheline.
Core1 modified the stop variable, but because stop is not volatile, this change can only occur in the Core1 cache, and the modified cacheline can theoretically never be brushed back to memory, In this way, the Core2 on the thread will never see the stop change.
Two: JIT angle:
Because the while loop in the Run method is executed many times, it is bound to trigger JIT compilation, the following to analyze the two cases of JIT-compiled results (triggered a number of JIT compiled, only the last C2 level JIT compiled results)
How to view the JIT assembly code please see my blog: How to use Hsdis and Jitwatch to view JIT assembler code under Windows platform
A. I is the case of a local variable within the Run method:
-
- The stop variable is detected at the first red box, and if true, then jump to L0001 to continue execution (the function exits at L0001), but stop is false, so this branch is not taken
- L0000,inc%EBP. That's i++.
- Test%eax, -0x239864a (%rip), polling the operation of SafePoint, can ignore
- JMP L0000, unconditionally jump back to L0000 to continue execution i++
If you rewrite the JIT-compiled code back, it's probably like this.
1 if (! stop) {2 while (true) {3 i++; 4 }5 }
Very explicit order reordering, the JVM felt that every time the loop to access the non-volatile type of stop variable is too wasteful, only at the beginning of the function to access a stop, subsequent regardless of how the stop variable, regardless of the change.
In the first case, this is how the dead loop comes in.
B. I is the case of a global volatile variable:
Start with the first red box:
-
- JMP L0001, jump to label L0001 unconditionally
- Movzbl 0x6c (%R10),%r8d; Access the static variable stop and copy it to the Register r8d
- Test%r8d,%r8d; Je L0000; If the value in r8d is 0, jump to L0000, otherwise continue to go down (function end)
- L000:mov 0x68 (%R10),%r8d; Access the static variable i and copy it to the Register r8d
- Inc%r8d; value in self-increment r8d
- mov%r8d, 0x68 (%R10); Copy the new value from the increment r8d back to the static variable I (the process of the above three lines is i++)
- Lock Addl $0x0, (%RSP); Adding 0 to the value in the RSP register, without any effect, is the key to the previous lock prefix, which causes the cache line to refresh, thus implementing the volatile semantics of the variable i
- Test%eax, -0x242a056 (%rip); Polling the safepoint operation, can ignore
- L0001, back to step 2.
That is, each loop accesses a stop variable, and sooner or later it accesses the new value (but not guaranteed) after the stop is modified, causing the loop to end.
The difference between the two scenarios is that the second case has access to the static volatile type variable i in the loop, which causes the JVM to be unable to make aggressive optimizations at JIT compile time, which is an additional effect.
Summarize
Related to the problem of memory visibility, we must use the happens-before principle of careful analysis. Because it's hard to know what strange optimizations the JVM has done behind the scenes.
Memory visibility, order reordering, JIT ... From a question of knowing