In the "Effective Java" book:
// Broken! - How long would you expect this program to run?
public class StopThread {
private static boolean stopRequested;
public static void main(String[] args) throws InterruptedException {
Thread backgroundThread = new Thread(new Runnable() {
public void run() {
int i = 0;
while (!stopRequested)
i++;
}
});
backgroundThread.start();
TimeUnit.SECONDS.sleep(1);
stopRequested = true;
}
}
the backgroundThread will not stop after one second. Because hoisting, the optimization in JVM, HotSpot server VM does.
You can view this in the following topic:
Why HotSpot will optimize the following using hoisting?.
The optimization goes like this:
if (!done)
while (true)
i++;
there are two ways to fix the problem.
private static volatile boolean stopRequested;
The function of volatile is
- forbid hoisting
- it guarantees that any thread that reads the field will see the most recently written value
public class StopThread {
private static boolean stopRequested;
private static synchronized void requestStop() {
stopRequested = true;
}
private static synchronized boolean stopRequested() {
return stopRequested;
}
public static void main(String[] args)
throws InterruptedException {
Thread backgroundThread = new Thread(new Runnable() {
public void run() {
int i = 0;
while (!stopRequested())
i++;
}
});
backgroundThread.start();
TimeUnit.SECONDS.sleep(1);
requestStop();
}
}
The above code is right in the Effective Java book, its equivalent that use volatile
to decorate the stopRequested
.
private static boolean stopRequested() {
return stopRequested;
}
If this method omit the synchronized
keyword, this program isn't working well.
I think that this change cause the hoisting when the method omit the synchronized
keyword.
Is that right?
To clearly understand why this happens you need to know something about what happens at a deeper level. (This is basically an explanation of what is called a happens-before relationship, with a language that I hope is more understable for the reader).
Normally variables are present in the RAM memory. When a thread needs to use them it take them from the RAM and put them in cache so it can access them as fast as possible until needed.
Using volatile
force a thread to read and write variables directly from RAM memory. So when many thread are using the same volatile
variable all them see the last version that is present in RAM memory and not a possible old copy in cache.
When a thread enter a synchronized
block it needs to take control of a monitor variable. All other threads wait until the first thread exit from the synchronized
block. To ensure that all thread can see the same modifications all variables used in a synchronized block are read and written directly from the RAM memory instead from a cache copy.
So if you try to read the variable stopRequested
without a synchronized
method or without a volatile
keyword you can read a possible old copy of it that is present in the cache.
To solve this you need to be sure that:
volatile
variables synchronized
block.Using the method
private static boolean stopRequested() {
return stopRequested;
}
without a synchronized
keyword and when stopRequested
is not volatile
means that you can read the value of stopRequested
from a cache copy that is invalid.