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.

11 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.

Anonymous said...

Hi Jeremy,

I found your blog when trying to find some answers to my questions on java concurrency & found your blog very useful.
Can you please help me understand this statement :

"It is safe to perform read-modify-write operations on shared volatile variables as long as you can ensure that the volatile variable is only written from a SINGLE thread. In this case you are confining the modifications to a single thread to prevent race conditions, & the visibility guarantees for volatile variables ensures that other threads see the most up-to-date value."

Now as i understand the "read-modify-write operation" they are referring to is x++ or the x-- operation which as you explained above is actually a 3 step operation -
r1 = v; //step1
r2 = r1 + 1; //step2
v = r2; //step3

So if only 1 thread (say thread1) is doing the x++ operation & while thread1 is in the middle of the operation say at step2 above, at that point in time another thread say thread2 reads x, then thread2 will not see the up-to-date value, even if only 1 thread is doing the writes/modifications.
The above statement says "visibility guarantees for volatile variables" ...
which i understand that when a variable is declared as volatile, then the runtime & CPU are told not to cache that variable but instead store it in main memory (and no re-ordering of statements related to volatile).
So even if a single thread is writing a volatile variable to the main memory, but if another thread reads x while thread1 was at step2, it would still see corrupt data.

So i am unclear how a single thread doing writes to a volatile variable does not have race conditions.

Thanks in advance.
Ila

Jeremy Manson said...

@ila - it wouldn't see a corrupt value. The value would be stale, but not corrupt.

This is a race condition, but it may be one that is acceptable in your program.

Note that it is *not* a data race, which has a more restrictive definition.