Java Memory Model - Composite Actions and Atomicity

[Updated: Feb 7, 2017, Created: Jun 23, 2016]

Composite actions like counter++ does not execute as a single operation. Instead, it is shorthand for a sequence of three discrete operations: fetch the current value, add one to it, and write the new value back to memory. This is an example of a read-modify-write operation, in which the resulting state is derived from the previous state. In Java these kind of operations are not overall atomic by default.


main

Assume two threads try to execute counter++ simultaneously without any kind of synchronization. As seen in the last tutorial to avoid visibility problem we have declared counter as volatile. Now assume the counter current value is 5, at some timing each of the two threads could read the value 5, add one to it, and each set the counter to 6. That means an increment got lost and the counter is now permanently off by one. In some scenarios this kind of behavior cannot be affordable per business logic.

To understand the problem let's see an example:


package com.logicbig.example;

public class CompositeActionWithVolatile {
    static volatile int c;

    public static void main (String[] args) throws InterruptedException {

        for (int t = 0; t < 10; t++) {
            c = 0;

            Thread thread1 = new Thread(() -> {
                for (int i = 0; i < 1000; i++) {
                    c++;
                }
            });

            Thread thread2 = new Thread(() -> {
                for (int i = 0; i < 1000; i++) {
                    c++;
                }
            });

            thread1.start();
            thread2.start();

            thread1.join();
            thread2.join();

            System.out.println("counter value = " + c);
        }

    }
}

We are expecting counter value of 2000 at the end of the main loop. Two threads are used to take advantage of multiple cores and improve calculation time as compared to using just one thread.
We are repeating the whole process 10 times to increase the chances of producing the problem we are investigating.

Output:

counter value = 1995
counter value = 2000
counter value = 1835
counter value = 2000
counter value = 1965
counter value = 1721
counter value = 2000
counter value = 1878
counter value = 2000
counter value = 2000

Process finished with exit code 0

Note that above output might vary for different kind of processor.

The machine I'm using:
    OS Name	Microsoft Windows 10 Home
    Version	10.0.10586 Build 10586
    Processor	Intel(R) Core(TM) i7-5700HQ CPU @ 2.70GHz, 2701 Mhz, 4 Core(s), 8 Logical Processor(s)
    Installed Physical Memory (RAM)	16.0 GB

What's the problem here?

In above output we are not getting output of 2000 every time, that's because the composite actions are not entirely atomic. At some timing the two threads read the same value of counter. We require our program to do gradual increment in counter variable by the two threads without overlapping reads and writes. All we need a total atomicity of counter++ composite operation.


How to fix the problem?

Using synchronized lock

public class CompositeActionWithSynchronized {
    static int c;

    public synchronized static void increase(){
        c++;
    }
    public static void main (String[] args) throws InterruptedException {

        for (int t = 0; t < 10; t++) {
            c = 0;

            Thread thread1 = new Thread(() -> {
                for (int i = 0; i < 1000; i++) {
                    increase();
                }
            });

            Thread thread2 = new Thread(() -> {
                for (int i = 0; i < 1000; i++) {
                    increase();
                }
            });

            thread1.start();
            thread2.start();

            thread1.join();
            thread2.join();

            System.out.println("counter value = " + c);
        }

    }
}

Output:

counter value = 2000
counter value = 2000
counter value = 2000
counter value = 2000
counter value = 2000
counter value = 2000
counter value = 2000
counter value = 2000
counter value = 2000
counter value = 2000

Process finished with exit code 0


Using java.util.concurrent.atomic.AtomicInteger

To fix the problem in our example, using AtomicInteger is the better choice because it implements a lock free mechanism to atomically increase the underlying int value.

public class CompositeActionWithAtomicVariable {
    static AtomicInteger c = new AtomicInteger();

    public static void main (String[] args) throws InterruptedException {

        for (int t = 0; t < 10; t++) {
            c.set(0);

            Thread thread1 = new Thread(() -> {
                for (int i = 0; i < 1000; i++) {
                    c.incrementAndGet();
                }
            });

            Thread thread2 = new Thread(() -> {
                for (int i = 0; i < 1000; i++) {
                    c.incrementAndGet();
                }
            });

            thread1.start();
            thread2.start();

            thread1.join();
            thread2.join();

            System.out.println("counter value = " + c.get());
        }
    }
}

Output:

    counter value = 2000
    counter value = 2000
    counter value = 2000
    counter value = 2000
    counter value = 2000
    counter value = 2000
    counter value = 2000
    counter value = 2000
    counter value = 2000
    counter value = 2000

    Process finished with exit code 0


java.util.concurrent.atomic package

Example Project

Dependencies and Technologies Used :

  • JDK 1.8
  • Maven 3.0.4

Composite Operations And Atomicity Select All Download
  • composite-action-atomicity-example
    • src
      • main
        • java
          • com
            • logicbig
              • example

See Also