Akash Divakar Gore Akash Divakar Gore - 1 month ago 8
Java Question

Data Races in an AtomicIntegerArray

In the code below:
I am updating

num[1]=0
of an
AtomicIntegerArray
num 1000 times each in 2 threads.

At the end of the 2 threads in main thread ;shouldn't the value of
num[1]
be 2000 as there shouldn't be data races in an
AtomicIntegerArray
.

However I get random values < 2000. Could someone tell me why?

Code:

import java.util.concurrent.atomic.AtomicIntegerArray;

public class AtomicIntegerArr {

private static AtomicIntegerArray num= new AtomicIntegerArray(2);

public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new MyRun1());
Thread t2 = new Thread(new MyRun2());

num.set(0, 10);
num.set(1, 0);

System.out.println("In Main num before:"+num.get(1));

t1.start();
t2.start();

t1.join();
t2.join();

System.out.println("In Main num after:"+num.get(1));
}

static class MyRun1 implements Runnable {
public void run() {
for (int i = 0; i < 1000; i++) {
num.set(1,num.get(1)+1);
}

}
}

static class MyRun2 implements Runnable {
public void run() {
for (int i = 0; i < 1000; i++) {
num.set(1,num.get(1)+1);
}

}

}

}


Edit: Adding
num.compareAndSet(1, num.get(1), num.get(1)+1);
instead of
num.set(1,num.get(1)+1);
doesnt work either.

Answer

This is a classic race condition. Any time you have a fetch, an operation, and a put, your code is racy.

Consider two threads, both executing num.set(1,num.get(1)+1) at roughly the "same time." First, let's break down what the expression itself is doing:

  • it fetches num.get(1); let's call this x
  • it adds 1 to that; let's call this y
  • it puts that sum in at `num.set(1, y);

Even though the intermediate values in your expression are just values on the stack, and not explicit variables, the operation is the same: get, add, put.

Okay, so back to our two threads. What if the operations are ordered like this?

inital state: n[1] = 5
Thread A      | Thread B
========================
x = n[1] = 5  |
              | x = n[1] = 5
              | y = 5 + 1 = 6
y = 5 + 1 = 6 | 
n[1] = 6      |
              | n[1] = 6

Since both threads fetched the value before either thread put its added value, they both do the same thing. You have 5 + 1 twice, and the result is 6, not 7!

What you want is getAndIncrement(int idx), or one of the similar methods that does the get, adding, and putting atomically.

These methods can actually all be built on top of the compareAndSet method you identified. But to do that, you need to do the increment within a loop, trying until the compareAndSet returns true. Also, for that to work, you have store that initial num.get(1) value in a local variable, rather than fetching it a second time. In effect, this loop says "keep trying the get-add-put logic until it works without anyone else having raced between the operations." In my example above, Thread B would have noticed that compareAndSet(1, 5, 6) fails (since the actual value at that time is 6, not 5 as expected), and thus retried. This is in fact what all of those atomic methods, like getAndIncrement, do.