Java Volatile Keyword Quick Tutorial
This is a quick tutorial of Java volatile keyword: a weaker synchronization. Details of memory visibility and performance influence will be discussed.
Volatile Keyword: A Weaker Synchronization Concept
What Java volatile keyword does is to make sure multiple threads see the latest value of a shared mutable variable if it is be declared as volatile. In this way, Java volatile keyword ensures visibility but it is still a weaker form of synchronization because it does not ensure atomicity.
That is, it does not block anything where multiple threads could actually enter a block of code that access (read, write) a shared mutable variable at the same time (even when that shared mutable variable is volatile), tof here is not blocking at all. This is made as a rule to keep in mind in “Java Concurrenty in Practice”:
Locking can gurantee both visibility and atomicity; Volatile variables can only guarantee visibility
Volatile Keyword: A Weaker Synchronization, When to Use it
As pointed out in previous section, volatile does not block anything. Reading or writing a volatile variable does not block threads reading or writing. As a result, we should not not rely too much on volatile varaibles for visibility which could be more fragile and harder to understand.
A bad case of using volatile variable would be using volatile counter values without any other synchronization. This is a case where a shared mutable counter variable is accessed from mutiple threads, and this counter could be in a mess state with incorrect values. This is easy to figure and try it yourself.
The following two cases might be good to use volatile variable:
- Most commonly, use volatile variables as a completion, interruption, or a status checking flag, which are all typically boolean type. This just indicates an importatnt life cycle event like shutdown has occurred, e.g., private volatile boolean stopped;
- With special care, we can manage to use volatile variable correctly still where multiple threads could even be writing to a shared mutable volatile variable, and still have the correct value stored in main memory, as long as the new value written to the variable does not depend on its current value or any previous values. In other words, a thread does not need to read a its value to figure out its next value when writing a value to the shared volatile variable.
Well, theoretically, according to “Java Concurrency in Practice”, we can use volitaile as long as the following 3 criterias are met:
- Writes to the variable do not depend on its current value, or you can ensure that only a single thread ever updates/writes the value;
- The variable does not participate in invariants with other state variables and
- Locking is not required for any other reason while the variable is being accessed.
For condition 2 and 3, I think it is too abstract to be a useful guidance in practice, actually just remember the rule is enough: “Locking can gurantee both visibility and atomicity; volitile variables can only guarantee visibility”.
Always think about this rule when using volatile keyword. So if in any cases we need atomicity, we should use a lock rather than volatile varaible and sometimes even consider atomic variables which serves as “better volatile variables” instead.
Volatile Keyword: Memory Visibility
Let’s start with a rule quoted from “Java Concurrency in Practice”:
Locking is not just about mutual exclusion; it is also about memory visibility. To ensure that all threads see the most up-to-date values of shared mutable variables, the reading and writing threads must synchronize on a common lock.
This is mainly talking about the risk of stale value, in other words, what this rule is trying to say is, everything thread A did in or prior to a synchronized block is visiable to thread B when B executes a synchronized block guarded by the same lock,
To understand this from the perspective of hardware, I think the latest value of that shared mutable variable will be stored into main memory first and then will be loaded into the local cache of each CPU where each thread resides in, this is how they see the latest value and this happens at the time of leaving/entering a synchronized block, I have the following conclusin which is not perfectly correct, but it helps remember what and when the value of a shared variable will be changed accross cache and memory:
The following figure can give a more visual picture to help
Note things could be a bit confusing as we know volatile variable does not have caching at all, for volatile varible, it reads from memory, not from any cache, so it always see the latest value. I still associate volatile variable with cache because the visibility effects of volatile varaibles extends beyond the value of the volatile variable itself. For the volatile varaible itself, it does not deal with any cache, but for varaibles before accessing a particular volatile variable, their cached value could be affected by the volatile variable, that is:
When thread A writes to a volatile vairable and subsequently thread B reads that same variable, the values of All variables that were visible to A prior to writing to the volatile variable become visible to B after reading the volatile variable.
This could be difficult to understand, but let’s take a look at a concrete example and we will know what this exactly mean, take a look at the following code:
1 2 3 4 5 6 7 8 9 10 11 12 13 // obj is a shared mutable object // except for the volatileVariable, // other fields of obj are not volatile variables // Thread A write: obj.f2 = 2; obj.volatileVariable = obj.volatileVariable + 1; // write obj.f1 = 1; // Thread B read: int v1 = obj.f1; int value = obj.volatileVariable; // read int v2 = obj.f2;
obj.f2 is written before writing to the volatile obj.volatileVariable, then both obj.f2 and obj.volatileVariable are written to main memory when Thread A writes to obj.volatileVariable. But obj.f1 is after the writing to the volatile variable, the value of obj.f1 will not be synchronized to main memory value. Note this just write values into main memory, to Thread B, it still only sees those stale values in its CPU’s local cache (see the above figure of memory model).
Now we examine what happens for Thread B. When thread B reads value of obj.f1, it still sees the old value of obj.f1 rather than the latest value of 1 written by Thread A, but when Thread B reads the volatile variable, both the obj.volatileVariable and obj.f2 are read from main memory into the local CPU cache used by Thread B. And when Thread B reads obj.f2 it will see the latest value of 2 written by Thread A.
This is quite an important detail of using volatile which people can easily forget and actually hard to understand. This could also be used as sort of an optimization where we do not have to declare everything as volatile but just the last one, however, I personaly do not recommend this because this could make things really difficult to understand.
Let’s understand this feature of “the effects of visibility extends beyond the volatile variable itself”, the question is why? I think what happens when a volatile variable is written, it will flush a whole part (i.e., those variables before the writing) of the cache memorty together with the volatile variable. My own guess is two reasons for this:
- it is not efficient to write a single value to main memory, and there is no additional burden of writing more values together as say a block, just write a block of cache memory is equally same amount of work of writing a single value
- this could serve as sort of a heuristic, values updated at the same time are synchronized together because they tend to be used at the same time, and this guarantees them all as up-to-date values.
The memory visibility issue is really tricky to explain, hopefully I make everything clear now, but feel free to leave your comments if you have better understanding.
Volatile Keyword: Performance Influence
Using volatile keywokd could have performance concerns, because:
- Reading and writing of volatile variables directly read or write to main memory. And obviously reading from and writing to main memory is more expensive than accessing the CPU cache.
- Accessing volatile variables also prevent instruction reordering which is a normal performance enhancement technique of Java JVM. This feature is actually called “Happens-Before Guarantee”.
The first one is already exlained and easy to understand.
The second point is saying that the reading and writing instructions of volatile variables cannot be reordered by the JVM. To understand the second point, let’s see the following code:
1 2 3 4 5 6 7 8 9 10 11 12 // obj is a shared mutable object // except for the volatileVariable, // other fields of obj are not volatile variables obj.f1 = 1; obj.f2 = 2; obj.f3 = 3; obj.volatileVariable = obj.volatileVariable + 1; int value1 = obj.f1; int value2 = obj.f2; int value3 = obj.f3;
As a optimization, the JVM could possible reorder the first 3 instructions of assignments (writing to f1, f2, f3), but it will make sure all of them happen before the volatile write instruction, and thus they will all be executed before the volatile write instruction).
After the volatile write, the JVM could again possibly reorder the 3 instructions after it (reading to value1, value2, value3) but it will again make sure these reads happens after the volatile write instruction.
So I discussed Java volatile keyword: weaker synchronization, when to use it, memory visibility and performance influence are given too. Hopefully, things become more clear to you now, and feel free to leave any comments.