In Java/Kotlin or any JVM languages, each thread has a "local memory" AKA. "Cache". When a thread want to write a variable into the memory, it first update the value in its own cache, then sync the modification into the Main Memory, which is shared between threads.
The following demo illustrates a use case that Thread unrealized changing happened:
class VolatileExample3 {
var flag = false
var a = 0
fun write() {
a = 1
Thread.sleep(10)
flag = true
}
companion object {
@JvmStatic
fun test() {
val example = VolatileExample3()
val th = Thread {
example.write()
}
th.start()
while (!example.flag) {}
println("a = ${example.a}")
}
}
}
The thread th calls the function write(), first update the value of a, then sleep for 10 ms as a simulated IO operation, finally set flag as true indicating that the "write" has been done.
In Main thread, it always querying the value of flag and skip while loop when flag is true, which is ought to be the time that write() is over.
We want the output as "a = 1", which is the correct answer after writing.
However, because the "local memory / cache" exists, the Main Thread will loop around in while as if it was stuck.
Main thread won't read Main Memory
Just before the content of write() run, main thread firstly read flag and get false, it won't get the value in Main Memory until we tell it explicitly.
Answer of the problem is obvious: add @Volatile to flag, in which the READ op onto it will make it's local memory invalid ahead of the real LOAD VALUE.
But I found another solution of this problem which is:
while (!example.flag) {
print("")
}
It works without @Volatile! I wandered whether the flush cache operation inside print() would make the local memory / cache of Main Thread invalid or not. If so, what is the mechanism?
Also I found that putting th.join() before the while loop also works. Are these methods essentially the same, which means that the termination of a thread would be noticed to all other threads leading to the invalidation of local memory?
There is no "cache" in the Java programming language. Caches are part of the underlying computer hardware architecture, and they work differently on different computers.
You job, as a Java programmer, is to make sure that your code obeys the rules that are published in the Java Language Specification (JLS). The job of the developers who provide the Java compiler (
javac), the Java virtual machine (JVM), and the Java run-time environment (JRE) for your particular computer is to ensure that if your code obeys the rules in the JLS, then their code will keep the promises made by the JLS.Last time I looked, the rules and promises regarding access to variables from multiple threads were mostly in Chapter 17 and, mostly expressed in terms of "synchronization" and "happens before" relationships.
That may work on your computer, with the operating system version and the JDK version that you happen to be using today. In fact, it works for a lot of people and many different computers, but it is not guaranteed to work.
Experimentation is not the way to find out what "works" and what does not. I once worked for a large corporation that released a major software package that broke the rules. It passed several weeks worth of aggressive testing in our offices. It even "worked" on all of our customer's computers when we first released it. But then, one of our customers upgraded their OS, and the software stopped working for them.
That is guaranteed to work. The JLS explicitly guarantees that whatever thread A does to shared variables must become visible to any other thread after the other thread has called
A.join(). In the language of the JLS, everything that thread A does "happens before" anA.join()call returns in the other thread.