Introduction
Volatile keyword is used to mark a variable as “being stored in main memory”. Every read of a volatile variable will be read from the computer’s main memory, and not from the CPU cache, and that every write to a volatile variable will be written to main memory.
In a multithreaded application where the threads operate on non-volatile variables, each thread may copy variables from main memory into a CPU cache while working on them. If computer contains more than one CPU, each thread may run on a different CPU. That means, that each thread may copy the variables into the CPU cache of different CPUs. With non-volatile variables there are no guarantees about when the Java Virtual Machine (JVM) reads data from main memory into CPU caches, or writes data from CPU caches to main memory. This can cause several problems.
// Declaration of volatile public class SharedObject { public volatile int counter = 0; }
volatile keyword guarantees this:
- If Thread A writes to a volatile variable and Thread B subsequently reads the same volatile variable, then all variables visible to Thread A before writing the volatile variable, will also be visible to Thread B after it has read the volatile variable.
- The reading and writing instructions of volatile variables cannot be reordered by the JVM. Instructions before and after can be reordered, but the volatile read or write cannot be mixed with these instructions. Whatever instructions follow a read or write of a volatile variable are guaranteed to happen after the read or write.
When a thread writes to a volatile variable, then not just the volatile variable itself is written to main memory. Also all other variables changed by the thread before writing to the volatile variable are also flushed to main memory. When a thread reads a volatile variable it will also read all other variables from main memory which were flushed to main memory together with the volatile variable.
Example
Read and write instructions of volatile variables cannot be reordered. Instructions before and after can be reordered, but the volatile read/write instruction cannot be reordered with any instruction occurring before or after it.
sharedObject.nonVolatile1 = 123; sharedObject.nonVolatile2 = 456; sharedObject.nonVolatile3 = 789; sharedObject.volatile = true; //a volatile variable int someValue1 = sharedObject.nonVolatile4; int someValue2 = sharedObject.nonVolatile5; int someValue3 = sharedObject.nonVolatile6;
The JVM may reorder the first 3 instructions, as long as all of them happens before the volatile write instruction (they must all be executed before the volatile write instruction). Similarly, the JVM may reorder the last 3 instructions as long as the volatile write instruction happens before all of them. None of the last 3 instructions can be reordered to before the volatile write instruction.
There are still situations where it is not enough to declare a variable volatile. If a thread needs to first read the value of a volatile variable, and based on that value generate a new value for the shared volatile variable, a volatile variable is no longer enough to guarantee correct visibility. The short time gap in between the reading of the volatile variable and the writing of its new value, creates an race condition where multiple threads might read the same value of the volatile variable, generate a new value for the variable, and when writing the value back to main memory – overwrite each other’s values.
In case only one thread reads and writes the value of a volatile variable and other threads only read the variable, then the reading threads are guaranteed to see the latest value written to the volatile variable. Without making the variable volatile, this would not be guaranteed.