I learned something interesting about the java volatile keyword the other day, and I thought I’d share it with the class.
First, some background:
What it does:
Volatile is a Java keyword which guarantees that any and every time the variable is read, you’ll get the most up to date version of the variable. This can be very useful in multi-threaded environments. Why volatile? From Dictionary.com (not in full):
vol⋅a⋅tile
- changeable; mercurial; flighty: a volatile disposition
- fleeting; transient: volatile beauty.
Simply put, the compiler is told not to let the bytes making up the volatile variable hang around in any registers or other forms of cache and so each time you ask to read this variable you’ll go get the most recent write from any thread.
The Java Memory Model requires that both fetch and store operations on 32-bit variables are atomic, but this does not apply to 64-bit operations – which can be considered as two 32-bit operations – unless the variable is volatile (or guarded by a lock). Thus, volatile variables also guarantee readiness/consistency for 64-bit primitives, e.g. longs and doubles.
What it doesn’t do:
Volatile variables do not guarantee atomicity, so compound functions like:
int x = 0;
int y = x++;
are not guaranteed to make y equal 1 as two threads running the above code simultaneously would cause problems (i.e. second line requires a ‘read x’, then an ‘increment x’. Another thread could change the value of x during that time).
With me so far? Good. Now, before I get to the interesting bit, quick recap on some concurrency pitfalls.
Bad code – Do not copy!
/*
* Not threadsafe!
*/
public class Foo {
private int x;
private int a,b;
public Foo {
x = 0;
}
public void setX(int newX) {
this.x = newX;
}
public void setAandB() {
a = x+1;
b = x+2;
// This could fail!
assert b > a;
}
}
This could fail in (at least) a couple of ways. Well, it’s the same way really, but there’s a couple of points I want to make.
Firstly, and most obviously, in a multi-threaded situation the value of x could be changed between lines 17 and 18 by a call to the setX() method. Ooops! BANG.
Now, let’s say we realise that we only ever need to increment x by 2 (and never decrement). So we try and fix the problem above by removing setX() and instead use incrementX(), like so:
Bad code – Do not copy!
/*
* Not threadsafe!
*/
public class Foo {
private int x;
private int a,b;
public Foo {
x = 0;
}
public void incrementX() {
this.x = x+2;
}
public void setAandB() {
a = x+1;
b = x+2;
// This could fail!
assert b > a;
}
}
As x can only be incremented, surely b must always be larger than a and the assertion will pass? Wrong! This can still fail because the Java Memory Model allows the compiler to change the order of code if there is no detectable change on the output of that single thread caused by doing so, regardless of how this might effect other threads.
That is to say, after compilation line 18 could be run before line 17. Now, your assertion will fail because, even tho the value of x can only ever increment, line 18 can be called first (b = 2), incrementX() may then be called from a second thread and then line 17 is called (a = 3). Ooops! BANG
But what’s this got to do with volatile variables, mrtom? Good question. And the answer is this.
Volatile variables force the compiler not to rearrange the order of your code.
So, if x is marked as volatile the second code sample will pass.
Better code – still not great mind!
/*
* Not threadsafe, but the assertion will pass
*/
public class Foo {
private volatile int x;
private int a,b;
public Foo {
x = 0;
}
public void incrementX() {
this.x = x+2;
}
public void setAandB() {
a = x+1;
b = x+2;
// This could fail!
assert b > a;
}
}
Personally I don’t really like using volatile in this way. It’s unclear and fragile. I’d much rather use a synchronized block or an explicit lock. But maybe because of that I don’t use volatile very often and, due to this, don’t know much about volatile.
If anyone can think of a concrete use case where using volatile like this has advantages let me know. I suspect there may be some performance improvement over a standard synchronized block but I’ve not got any data to support this.
Better still
/*
* Threadsafe
*/
public class Foo {
private Object lock;
private volatile int x;
private int a,b;
public Foo {
x = 0;
}
public void incrementX() {
synchronzied(lock) {
this.x = x+2;
}
}
public void setAandB() {
synchronized(lock) {
a = x+1;
b = x+2;
}
// This can't fail anymore
assert b > a;
}
}
References/Further reading:
- Java Concurrency In Practise and
- Java Language Specification (third edition) 8.3.1.4