Lets save I have this code which exhibits stale cache reads by a thread, which prevent it from exiting its while loop.
class MyRunnable implements Runnable {
boolean keepGoing = true; // volatile fixes visibility
@Override public void run() {
while ( keepGoing ) {
// synchronized (this) { } // fixes visibility
// Thread.yield(); // fixes visibility
System.out.println(); // fixes visibility
}
}
}
class Example {
public static void main(String[] args) throws InterruptedException{
MyRunnable myRunnable = new MyRunnable();
new Thread(myRunnable).start();
Thread.sleep(100);
myRunnable.keepGoing = false;
}
}
I believe the Java Memory Model guarantees that all writes to a volatile variable synchronize with all subsequent reads from any thread, so that solves the problem.
If my understanding is correct the code generated by a synchronized block also flushes out all pending reads and writes, which serves as a kind of "memory barrier" and fixes the issue.
From practice I've seen that inserting yield
and println
also makes the variable change visible to the thread and it exits correctly. My question is:
Is yield/println/io serving as a memory barrier guaranteed in some way by the JMM or is it a lucky side effect that cannot be guaranteed to work?
Edit: At least some of the assumptions I made in the phrasing of this question were wrong, for example the one concerning synchronized blocks. I encourage readers of the question to read the corrections in the answers posted below.
Nothing in the specification guarantees flushing of any kind. This simply is the wrong mental model, assuming that there has to be something like a main memory that maintains a global state. But an execution environment could have local memory at each CPU without a main memory at all. So CPU 1 sending updated data to CPU 2 would not imply that CPU 3 knows about it.
In practice, systems have a main memory, but caches may get synchronized without the need to transfer the data to the main memory.
Further, discussing memory transfers end up in a tunnel vision. Java’s memory model also dictates, which optimizations a JVM may perform and which not. E.g.
nonVolatileVar = null;
Thread.sleep(100_000);
if(nonVolatileVar == null) {
// do something
}
Here, the compiler is entitled to remove the condition, and perform the block unconditionally, as the preceding statement (ignoring the sleep) has written null
and other thread’s activities are irrelevant for non-volatile
variables, regardless of how much time has elapsed.
So when this optimization has been performed, it doesn’t matter how many threads write a new value to this variable and “flush to memory”. This code won’t notice.
So let’s consult the specification
It is important to note that neither
Thread.sleep
norThread.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 toThread.sleep
orThread.yield
, nor does the compiler have to reload values cached in registers after a call toThread.sleep
orThread.yield
.
I think, the answer to your question couldn’t be more explicit.
For completeness
I believe the Java Memory Model guarantees that all writes to a volatile variable synchronize with all subsequent reads from any thread, so that solves the problem.
All writes made prior to writing to a volatile
variable will become visible to threads subsequently reading the same variable. So in your case, declaring keepGoing
as volatile
will fix the issue, as both threads consistently use it.
If my understanding is correct the code generated by a synchronized block also flushes out all pending reads and writes, which serves as a kind of "memory barrier" and fixes the issue.
A thread leaving a synchronized
block establishes a happens-before relationship to a thread entering a synchronized
block using the same object. If using a synchronized
block in one thread appears to solve the issue despite you’re not using a synchronized
block in the other, you’re relying on side effects of a particular implementation which is not guaranteed to continue to work.