Atomic package

Atomicity in thread-safe parallel programming is one of crucial points. Thanks to it, any thread participating in the parallel code execution receives the final value, without the risk to get an "intermediate" value modified by another thread.

This article presents this, according to atomic package Javadoc, "small toolkit of classes that support lock-free thread-safe programming on single variables". The first part tries to explain shortly the idea of atomicity. After, it focuses on the content on atomic package. The last part shows the features on real code, through usual test cases.

Atomicity in parallel programming

To understand well atomic operations, nothing better than an example of behavior of non-atomic operation. Imagine that there are a program which increments one counter, shared by several threads. The counter begins with 0. Now, let's see what can happen if two threads work on it without any synchronization mechanism:

The result given by these operations will be incorrect because the number of 2 is expected. One of responses for this kind of synchronization problems are atomic operations. This kind of operations consists on fact that any thread participating in parallel computation sees the operations results as single and atomic. Our instrumentation case written as atomic operation, could give below result:

Atomic package

In Java, atomic operations can be produced in several ways: by making an object volatile, by using synchronization mechanisms, such as synchronize blocks, or by working with atomic objects. Atomic objects are more powerful than synchronized blocks because they don't use any locking mechanism. Instead, they prefer to work with machine instruction called call-and-swap (CAS). They are also thought as richer and more generalized versions of volatile variables because of support for this instruction.

But what CAS is ? CAS is represented by compareAnd(Set|Update) operations. They accept 3 parameters: memory address of changed value, new value and expected value after writing. If both values are the same, operation succeeds. Otherwise, caller can either retry to update or do nothing. To understand this technique, let's take a look on java.util.concurrent.atomic.AtomicInteger class two methods using the CAS principle:

public final int getAndUpdate(IntUnaryOperator updateFunction) {
  int prev, next;
  do {
    prev = get();
    next = updateFunction.applyAsInt(prev);
  } while (!compareAndSet(prev, next));
  return prev;
}

public final boolean compareAndSet(int expect, int update) {
  return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}

As you can observe, getAndUpdate(IntUnaryOperator) tries to change the value held by AtomicInteger thanks to do-while loop. But to do so, it invokes compareAndSet(int, int) which uses low-level operations executed by the instance of sun.misc.Unsafe. As the name indicates, compareAndSwapInt uses internally CAS mechanism to update the value. It returns true when the update was successfully made. By doing so, getAndUpdate method can try to update the value safely, without the need of locking any resource with synchronized block or any other available method.

As described in Javadoc, atomic objects follow the memory rules defined for volatile variables. We can also read that atomic objects are here to be used in non-blocking data structures. But they are not there to replace classical Integer, Long and other wrapper objects of primitive types. Atomic objects are supposed to be modified very often, in small lapse of time, in concurrent environment.

Example of atomic objects

To understand benefits of atomic objects, we'll compare the same code represented with atomic object and with classical Java object. The test concerns incrementation and the code with classical Java objects contains on purpose thread-safety leaks. Note however that the code with classical Java objects won't fail every time. This proves another drawback - the unpredictability. And the unpredictable code is the unmaintainable code.

Test cases look like below:

public class AtomicObjects {

  @Test
  public void should_correctly_update_atomic_objects() throws InterruptedException {
    ExecutorService executor = Executors.newFixedThreadPool(5);
    for (int i = 0; i < 5; i++) {
      executor.execute(new Runnable() { 
        @Override
        public void run() {
          AtomicCounter.increment();
        }
      });
    }
    executor.awaitTermination(4, TimeUnit.SECONDS);

    assertThat(AtomicCounter.VALUE.get()).isEqualTo(5);
  }

  @Test
  public void should_produce_concurrency_errors_on_not_synchronized_incrementation() throws InterruptedException {
    for (int i = 0; i < 15; i++) {
      new Thread(new Runnable() {
        @Override
        public void run() {
          NotAtomicCounter.increment();
        }
      }).start();
    }

    Thread.sleep(2000); // give the time to all threads
     /**
      * There are no general rule. This test will fail from time to time.
      */
    assertThat(NotAtomicCounter.VALUE).isNotEqualTo(15);
  }

  // Not thread-safe counter
  private static final class NotAtomicCounter {
    public static Integer VALUE = 0;

    public static void increment() {
      VALUE++;
    }
  }

  // Thread safe counter
  private static final class AtomicCounter {
    private static AtomicInteger VALUE = new AtomicInteger(0);

    public static void increment() {
      VALUE.incrementAndGet();
    }
  }
}

Through this article we can see that atomic objects are simple in use and thanks to CAS mechanisms, they don't need explicit synchronization to work in concurrent environments. This not the case of not atomic Java objects, such as Integers, which without any support, can't work correctly in multi-threading programs.


If you liked it, you should read:

📚 Newsletter Get new posts, recommended reading and other exclusive information every week. SPAM free - no 3rd party ads, only the information about waitingforcode!