1

I had a question related to accessing individual elements via an Atomic Reference. If I have an IntegerArray and an atomic reference to it;will reading and writing to individual elements of the array via the AtomicReference variable cause data races?

In the code below: num is an Integer Array with aRnumbers being the atomic reference to the array. In threads 1 and 2; I access aRnumbers.get()[1] and increment it by 1.

I am able to access individual elements via the atomic reference without data races to accurate results each time with 22 as the output of aRnumbers.get()[1] in the main thread after both threads complete.

But,since the atomic reference is defined on the array and not on the individual elements; shouldn't there be a data race in this case leading to 21/22 as the output?

Isn't having data races in this case the motivation for having a AtomicIntegerArray data structure which provides a separate AtomicReference to each element?

Please find below the java code that i am trying to run.Could anyone kindly let me know where I am going wrong.

import java.util.concurrent.atomic.AtomicReference;

public class AtomicReferenceExample {


    private static int[] num= new int[2];
    private static AtomicReference<int[]> aRnumbers;

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

        num[0]=10;
        num[1]=20;

        aRnumbers = new AtomicReference<int[]>(num);

        System.out.println("In Main before:"+aRnumbers.get()[0]+aRnumbers.get()[1]);

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

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

        System.out.println("In Main after:"+aRnumbers.get()[0]+aRnumbers.get()[1]);
    }

    static class MyRun1 implements Runnable {
        public void run() {
            System.out.println("In T1 before:"+aRnumbers.get()[1]);
            aRnumbers.get()[1]=aRnumbers.get()[1]+1;

        }
    }

    static class MyRun2 implements Runnable {
        public void run() {
            System.out.println("In T2 before:"+aRnumbers.get()[1]);
            aRnumbers.get()[1]=aRnumbers.get()[1]+1;

        }

    }

}
Olivier Jacot-Descombes
  • 104,806
  • 13
  • 138
  • 188

2 Answers2

0

shouldn't there be a data race in this case leading to 21/22 as the output?

Indeed there is. Your thread are so short lived that most likely they are not running at the same time.

Isn't having data races in this case the motivation for having a AtomicIntegerArray data structure which provides a separate AtomicReference to each element?

Yes, it is.

Could anyone kindly let me know where I am going wrong.

Starting a thread takes 1 - 10 milli-seconds.

Incrementing a value like this even without the code being JITed is likely to take << 50 microseconds. If it was optimised it would take about 50 - 200 nano-seconds per increment.

As starting athread takes about 20 - 200x longer than the operating they won't be running at the same time so there is no race condition.

Try incrementing the value a few million times, so you have a race condition because both threads are running at the same time.

Peter Lawrey
  • 525,659
  • 79
  • 751
  • 1,130
  • Yes perfect !! I initialized num[1] to 0 and incremented the reference 1000 times each in the 2 threads with the expected output now being 2000 and yes,I did get dataraces with 2000 never being the final output . Thanks Peter,I really thought I had messed something up in my understanding. – Akash Divakar Gore Oct 30 '16 at 20:45
  • @AkashDivakarGore Just that threads don't start and quickly as you might think. – Peter Lawrey Oct 30 '16 at 20:46
  • Yes,I didn't think on those lines. Never been happier on seeing data races!! :P ! Sigh!! , I can take a breather now. More questions to follow once I write and execute a few more codes on those lines (Array of Atomic Integers vs AtomicArray of Integers) – Akash Divakar Gore Oct 30 '16 at 20:56
0

Incrementing an element consists of three steps:

  1. Reading the value.
  2. Incrementing the value.
  3. Writing the value back.

A race condition can occur. Take an example: Thread 1 reads the value (let's say 20). Task switch. Thread 2 reads the value (20 again), increments it and writes it back (21). Task switch. The first thread increments the value and writes it back (21). So while 2 incrementing operations took place, the final value is still incremented only by one.

The data structure does not help in this case. A thread safe collection helps keeping the structure consistent when concurrent threads are adding, accessing and removing elements. But here you need to lock access to an element during the three steps of the increment operation.

Olivier Jacot-Descombes
  • 104,806
  • 13
  • 138
  • 188
  • Yes, that was supposed to happen as per my understanding too. Unfortunately(or rather fortunately) it didnt and I got 22 as the final value. I initialized num[1] to 0 and incremented the reference 1000 times each in the 2 threads with the expected output now being 2000 and yes,I did get dataraces with 2000 never being the final output . for (int i=0;i<1000;i++) { aRnumbers.get()[1]=num[1]+1; } Thanks Olivier – Akash Divakar Gore Oct 30 '16 at 20:39
  • As others have suggested try 1,000,000 times. That's nothing for modern CPUs. – Olivier Jacot-Descombes Oct 30 '16 at 21:12