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.

9 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 comment has been removed by a blog administrator.
SkaRootz said...

If volatile doesn't guarantee the atomicity... so what it does?? I'm a little confused

Jeremy Manson said...

Volatile does this:

http://jeremymanson.blogspot.com/2008/11/what-volatile-means-in-java.html

yura said...

Hi Jeremy, thank you for the information in this post. I found it useful for myself, can you help me with following questions?
Question(1)
Can you help me understand a problem in the following snipped. I think it is ordering problem, because I see delta in results only equals to "1". I'm not saying that the following code is protected from the visibility problem, but what I'm seeing in the results is related to ordering, correct?

When I use the local synchronization on "lock" object (commented out code) or volatile definition, I don't see the problem.

public class ShowVolatile {

private /*volatile*/ long x = 0;
private /*volatile*/ long y = 0;
private /*volatile*/ static boolean b = false;

//private static Object lock = new Object();
private static ShowVolatile sv = new ShowVolatile();

public static void main(String... args) {

System.out.println("start TH1");
new Thread(new Runnable() {
public void run() {
System.out.println("started TH1");
while (true) {
//synchronized (lock) {
sv.x += 1;
sv.y += 1;
b = true;
//}
while (b) {
Thread.yield();
}
}
}
}).start();

while (!b) {
Thread.yield();
}

System.out.println("start TH2");
new Thread(new Runnable() {
public void run() {
System.out.println("started TH2");
while (true) {

while (!b) {
Thread.yield();
}

//synchronized (lock) {
long x = sv.x;
long y = sv.y;
if ((x != y)) {
System.out.println("got it, x: " + x + " y: " + y);
System.out.println(" sv.x: " + sv.x + " sv.y: " + sv.y);
System.out.println("-------------------------------------------");
// System.exit(-1);
}

b = false;
//}
}
}
}).start();
}
}

results on linux x64 (kernel 3.0), i7, oracle java 1.7:
got it, x: 3193371056 y: 3193371055
got it, sv.x: 3193371056 sv.y: 3193371056
-------------------------------------------
got it, x: 3548566839 y: 3548566838
got it, sv.x: 3548566839 sv.y: 3548566839
-------------------------------------------
got it, x: 3556046058 y: 3556046057
got it, sv.x: 3556046058 sv.y: 3556046058
-------------------------------------------
got it, x: 3566294270 y: 3566294269
got it, sv.x: 3566294270 sv.y: 3566294270
-------------------------------------------

Question (2)
In case I un-comment the "lock" object & synchronized blocks, I don't see the problem. I think it is because it fixes the ordering problem. Does this means that this change fixes the visibility problem as well? or I should use the volatile keyword for x, y and b (or at least for one of them) to fix the visibility problem?

Jeremy Manson said...

@yura - this isn't really an ordering issue. This is a plain old lost update. TH2 is reading x and y between the increment to x and the increment to y, so they are different. Locking fixes this by ensuring that they can only be read after they are both incremented.

~SS~ said...

Could you please also explain when an atomic variables need to be Volatile or Vice versa?

Jeremy Manson said...

@~SS~: If you are still paying attention, I'm not sure I know what you mean by an "atomic variable". If you mean an object with a type of java.util.concurrent.atomic.AtomicSomething, then it is probably the reference to that object that will be volatile, not the object itself. The reference to that variable needs to be protected by synchronization just like any other variable does.