Volatile fields are present from a long time but even today they are a big unknown. In times where most machines had only a processor, although possibly necessary, we could write programs without having to deal with them. But in these days, with the arrival of multiprocessor systems, volatile fields have become vital. I am not going into details as there are a lot of resources in the web about them.
Let's say that volatile fields allow us to make fields visible between thread. A situation where it can be useful is the case when a flag can be set and unset by different thread concurrently. Suppose we need to turn the debug mode on/off in runtime. We could have a global flag field accessible by all threads that read it to know if they have to print traces. For example:
class Global {
static int debugLevel = false;
static boolean debugFlag = 0;
static void turnOnDebug(int level) {
debugLevel = level;
debugFlag = true;
}
static void turnOffDebug() {
debugFlag = false;
}
}
class Worker {
void doWork() {
...
if (Global.debugFlag) {
if (level>=1) {
System.out.println("INFO: in doWork method");
}
if (level>=2) {
System.out.println("DEBUG: more detailed information");
}
}
...
}
}
What would happen if a thread calls Global.turnOnDebug(2)?
The logical answer is that all threads would start printing traces but what really happends is that it is not guaranteed that other threads see debugFlag as true.
How come?
Because of visibility of fields as debugFlag field can be cached without being placed in main memory. That implies that the other threads see an outdated value of this field.
Any other surprise?
Yes, even in case of the other threads noticed the change of the field debugFlag, they could see the level value as 0!
How come?
Because of a technique used to improve performance called reordering. In this case, when turnOnDebug is called, the compiler is free to execute the sentence setting debugFlag as true before setting debugLevel. Then, if just before setting the level another thread is scheduled and executes the doWork method, it could see the field debugFlag as true and level as 0.
Well, to solve this, we can use volatile fields. A detailed explanation using the happens-before idiom can be read in JSR133, here I will only put it in practice.
We could access this field using synchronized blocks, but it would be better in performance terms to define the debugFlag field as volatile:
volatile static boolean debugFlag = 0;
Volatile fields are a lot lighter than synchronized blocks and are guaranteed to never block.
Why?
Reads of volatile fields are guaranteed to be updated!!!
Compiler assures that the field is placed in memory (no cache) before the read is made.
Shouldn't we make debugLevel volatile as well?
We could, but it is not necessary because when a thread reads a volatile field, it is guaranteed that the values of previous fields written are updated and visible from the reader thread. That means that the new value of debugLevel is sure to be in memory after reading the volatile field and this implies that the reordering commented before cannot take place.
As said before, all described here can be expressed formally using the happens-before idiom described in JSR133 and in the Java Memory Model of recent revisions of JLS (Java Language Specification). It applies officially since Java 1.5(unofficially since latest revisions of Java 1.4). Before this version, Java Memory Model had some issues and we had to use synchronized blocks instead of lighter volatile fields.
No comments:
Post a Comment