221. Atomic variables

A naive approach for counting all numbers from 1 to 1,000,000 via  Runnable may look as follows:

public class Incrementator implements Runnable {

public [static] int count = 0;

@Override
public void run() {
count++;
}

public int getCount() {
return count;
}
}

And, let's spin-up five threads that will increment the count variable concurrently:

Incrementator nonAtomicInc = new Incrementator();
ExecutorService executor = Executors.newFixedThreadPool(5);

for (int i = 0; i < 1 _000_000; i++) {
executor.execute(nonAtomicInc);
}

But, if we run this code several times, we get different results as follows:

997776, 997122, 997681 ...

So, why don't we get the expected result, 1,000,000? The reason is because count++ is not an atomic operation/action. It consists of three atomic bytecode instructions:

iload_1
iinc 1, 1
istore_1

During one thread, read the count value and increment it by one, and another thread reads the older value leading to a wrong result. In a multi-threading application, the scheduler can halt the execution of the current thread between each of these bytecode instructions and start a new thread, which works on the same variable. We can fix things via synchronization or, even better, via atomic variables.

Atomic variable classes are available in java.util.concurrent.atomic. They are wrapper classes that limit the scope of contention to a single variable; they are much more lightweight than Java synchronization and are based on CAS (short for Compare and Swap: modern CPUs support this technique in which it compares the content of a given memory location with a given value and updates it to a new value if the current value equals the expected value). Mainly, these are atomic compound actions that affect a single value in a lock-free manner similar to volatile. The most used atomic variables are the scalars:

  • AtomicInteger
  • AtomicLong
  • AtomicBoolean
  • AtomicReference

And, the following are for arrays:

  • AtomicIntegerArray
  • AtomicLongArray
  • AtomicReferenceArray

Let's rewrite our example via AtomicInteger:

public class AtomicIncrementator implements Runnable {

public static AtomicInteger count = new AtomicInteger();

@Override
public void run() {
count.incrementAndGet();
}

public int getCount() {
return count.get();
}
}

Notice that, instead of count++, we wrote count.incrementAndGet(). This is just one of the methods provided by AtomicInteger. This method atomically increments the variable and returns the new value. This time, the count will be 1,000,000.

The following table contains several methods of AtomicInteger that are commonly used. The left column contains the methods, while the right column contains the non-atomic meaning:

AtomicInteger ai = new AtomicInteger(0); // atomic
int i = 0; // non-atomic

// and
int q = 5;
int r;

// and
int e = 0;
boolean b;

Atomic operation

Non-atomic counterpart

r = ai.get();

r = i;

ai.set(q);

i = q;

r = ai.incrementAndGet();

r = ++i;

r = ai.getAndIncrement();

r = i++;

r = ai.decrementAndGet();

r = --i;

r = ai.getAndDecrement();

r = i--;

r = ai.addAndGet(q);

i = i + q; r = i;

r = ai.getAndAdd(q);

r = i; i = i + q;

r = ai.getAndSet(q);

r = i; i = q;

b = ai.compareAndSet(e, q);

if (i == e) { i = q; return true; } else { return false; }

 

Let's tackle several problems via atomic operations:

  • Update the elements of an array via updateAndGet​(IntUnaryOperator updateFunction):

// [9, 16, 4, 25]
AtomicIntegerArray atomicArray
= new AtomicIntegerArray(new int[] {3, 4, 2, 5});

for (int i = 0; i < atomicArray.length(); i++) {
atomicArray.updateAndGet(i, elem -> elem * elem);
}
  • Update a single integer via updateAndGet​(IntUnaryOperator updateFunction):

// 15
AtomicInteger nr = new AtomicInteger(3);
int result = nr.updateAndGet(x -> 5 * x);
  • Update a single integer via accumulateAndGet​(int x, IntBinaryOperator accumulatorFunction):

// 15
AtomicInteger nr = new AtomicInteger(3);
// x = 3, y = 5
int result = nr.accumulateAndGet(5, (x, y) -> x * y);
  • Update a single integer via addAndGet​(int delta):

// 7
AtomicInteger nr = new AtomicInteger(3);
int result = nr.addAndGet(4);
  • Update a single integer via compareAndSet​(int expectedValue, int newValue):

// 5, true
AtomicInteger nr = new AtomicInteger(3);
boolean wasSet = nr.compareAndSet(3, 5);

Starting with JDK 9, atomic variable classes have been enriched with several methods such as get/setPlain(), get/setOpaque(), getAcquire(), and their companions. To gain an understanding of these methods, have a look at Using JDK 9 Memory Order Modes by Doug Lea, available at http://gee.cs.oswego.edu/dl/html/j9mm.html, at the time of writing.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.135.198.174