Tuesday, August 21, 2007

Volatile Does Not Mean Atomic!

Here's a question I get a lot. Is the following code snippet "thread safe"?


volatile int v = 0;

Thread 1:
v++;

Thread 2:
v--;

The question asks what the possible results of this code are; the questioners usually want the answer "v can only be 0 after this code is run".

This isn't the way it works! If you do an increment of a volatile integer, you are actually performing three separate operations:

  1. Read the integer to a local.

  2. Increment the local.

  3. Write the integer back out to the volatile field.


So what you really have is this:

volatile int v = 0;

Thread 1:
r1 = v;
r2 = r1 + 1;
v = r2;

Thread 2:
r3 = v;
r4 = r3 - 1;
v = r4;

So, if Threads 1 and 2 both read v and see the value 0, then Thread 1 will write 1 to it and Thread 2 will write -1 to it. You are not guaranteed to see the value 0!

If you want an atomic increment (or decrement), you have to use the java.util.concurrent.atomic classes, which allow you to create object that represent numbers that can be incremented or decremented atomically. The VM is smart enough to replace the objects with plain ol' numbers (a process that I claim is called intrinsification), which it then uses atomic machine instructions to manipulate.

So beware!

ETA: There was a bit of confusion about atomicity after I posted this. For more on atomicity, visibility and ordering, check out this post.

3 comments:

Jonathan said...

Hi Jeremy. The Java spec says this:

Another approach would be to declare i and j to be volatile:

class Test {
  static volatile int i = 0, j = 0;
  static void one() { i++; j++; }
  static void two() {
   System.out.println("i=" + i + " j=" + j);
  }
}

This allows method one and method two to be executed concurrently, but guarantees that accesses to the shared values for i and j occur exactly as many times, and in exactly the same order, as they appear to occur during execution of the program text by each thread. Therefore, the shared value for j is never greater than that for i, because each update to i must be reflected in the shared value for i before the update to j occurs.

This seems to conflict your statement.

Jeremy Manson said...

Hi, Jonathan.

You are misreading the spec. This is referring to an ordering constraint, not an atomicity constraint. I should do a full posting about what the difference is, but here's the Reader's Digest version:

The example you gave is about what happens when one thread writes to two volatile variables, and another thread reads from those variables. In that case, the updates to the variables will appear to occur in the same order to both threads (so you can't see the value 0 for i and the value 1 for j, because that would mean that other thread saw the update to i without seeing the update to j).

My posting was about what happens when two threads write to one volatile variable simultaneously. Because two threads are doing it, there's no guaranteed ordering between the respective increment operations, so you end up with a result that depends on the interleaving of the two threads -- you can get a different result depending on what the interleavings are.

Alex said...
This post has been removed by a blog administrator.