Let me start off with a modification to your example:
public class Test {
public static long[] arr = new long[20]; // Make this non-volatile now
public static volatile int vol = 0; // Make another volatile variable
public static void main(String[] args) throws Exception {
new Thread(new Thread(){
@Override
public void run() {
//Thread A
try {
TimeUnit.MILLISECONDS.sleep(1000);
arr[19] = 2;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
new Thread(new Thread(){
@Override
public void run() {
//Thread B
while (true) {
int i = vol;
if (arr[19] == 2) {
break;
}
}
System.out.println("Jump out of the loop!");
}
}).start();
}
}
You will realize that this will also cause Thread B to jump out of the loop (unless this symptom is something that is JIT-specific and mine happens to do this). The magic is this int i = vol;
- or more precisely, the read of a volatile
variable. Removing that line will cause Thread B to stay infinitely inside the loop.
Therefore, it seems that any read to a volatile
variable (i.e. any volatile reads) seem to retrieve the most updated values (including other non-volatile values).
I tried to look into JLS but it is too complex for me to comprehend fully. I saw an article here that describes the visibility guarantee.
From the article:
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.
(Ignore the typo of "all all" which is in the article.)
In this case, it seems that all the data from the main memory will be updated back to the CPU cache when a volatile
variable is read by the thread.
Additional interesting findings: If you add another Thread.sleep()
to Thread B (e.g. sleep for 50ms), the loop would manage to exit even without the read to the volatile
variable. Surprising JLS 17.3 states this:
It is important to note that neither Thread.sleep nor Thread.yield
have any synchronization semantics. In particular, the compiler does
not have to flush writes cached in registers out to shared memory
before a call to Thread.sleep or Thread.yield, nor does the compiler
have to reload values cached in registers after a call to Thread.sleep
or Thread.yield.
Again, I'm not sure if this symptom is JIT or JRE-specific again.
Thread
just to use it as aRunnable
for another thread. You probably can drop the innerThread
and just callstart()
on the anonymousThread
subclass each time. – Earthling