Java Concurrency - Safely modify numbers with AtomicInteger

dimitrilc 2 Tallied Votes 79 Views Share

Introduction

Whenever we want to modify numbers from multiple threads running concurrently, such as modifying a counter, one of the best options is to use the AtomicX classes. The most basic numeric atomic classes are AtomicInteger and AtomicLong.

In this tutorial, we will learn how to use the AtomicInteger class to atomically update numbers as well as some of its common operators.

Goals

At the end of the tutorial, you would have learned:

  1. How to use AtomicInteger to make numerics thread-safe.

Prerequisite Knowledge

  1. Intermediate Java.
  2. Basic Java Concurrency.

Tools Required

  1. A Java IDE such as IntelliJ Community Edition.

Project Setup

To follow along with the tutorial, perform the steps below:

  1. Create a new Java project.
  2. Create a package com.example.
  3. Create a class called Entry.
  4. Create the main() method inside Entry.java.

Concept Overview

Before learning how to use AtomicInteger, let us take a look at what problem it solves. First, we will try to modify a regular int from multiple threads, so create a static counterproperty for the Entry class.

static int counter;

And then add the method called unsafe() from the code below into the Entry class as well.

private static void unsafe(){
   ExecutorService executorService = Executors.newCachedThreadPool();

   for (int i = 0; i < 5; i++){
       executorService.submit(() -> {
           System.out.println(counter++);
       });
   }

   try {
       executorService.awaitTermination(1, TimeUnit.SECONDS);
   } catch (InterruptedException e) {
       e.printStackTrace();
   } finally {
       executorService.shutdown();
   }
}

We chose the name unsafe() to signify that modifying an int from multiple threads in this case is not thread safe. What the method above does can be summarized in a few words:

  1. It submits 5 Runnables into a Thread pool. This particular ExecutorService creates new Threads as needed.
  2. The Runnable lambdas try to print the counter value, and the counter value is also incrementing at the same time.
  3. The ExecutorService waits for one second before timing out.

When we call this method in main() with

unsafe();

We might get some output that repeats the same number multiple times. The output that I received on my computer is:

1
3
2
0
0

As you can see, the code fails to print the number 4 and printed zero twice. This unpredictable behavior is explained below:

  1. The postfix increment operator(x++) is actually a shortcut for 3 different operations: return the value, increment the value, and then reassign the value.
  2. There are multiple threads vying to do all 3 tasks in parallel, so one thread might have received a value of the variable counter before another thread was able to assign a new value for it.

AtomicInteger

To fix the problem presented in the previous section, we can use an AtomicInteger instead of an int. An AtomicInteger prevents thread interference by only allowing one thread to modify the underlying value at a time.

Add a new atomic variable in the Entry class from the code below.

static AtomicInteger atomicCounter = new AtomicInteger();

And then create a new method called safe() in the Entry class as well.

private static void safe(){
   ExecutorService executorService = Executors.newCachedThreadPool();

   for (int i = 0; i < 5; i++){
       executorService.submit(() -> {
           System.out.println(atomicCounter.getAndIncrement());
       });
   }

   try {
       executorService.awaitTermination(1, TimeUnit.SECONDS);
   } catch (InterruptedException e) {
       e.printStackTrace();
   } finally {
       executorService.shutdown();
   }
}

Because we cannot use Java operators on AtomicInteger, we replaced the postfix operator(x++) with a method called getAndIncrement(). Comment out the unsafe() call used previously and call safe() from main().

//unsafe();
safe();

We can see that the output does not contain any duplicate or missing number. 0-4 are all printed:

0
2
1
3
4

Even though the incrementing and assigning operations are synchronized, the println() calls are not, so we still would not see the digits from increasing order(0 -> 1 -> 2 -> 3 -> 4).

AtomicInteger operators

Because we are not able to use Java operators on an AtomicInteger reference, we will have to resort to these methods in place of their respective operators. Below are some of the most common.

  1. Postfix increment(x++) : getAndIncrement()
  2. Postfix decrement(x--) : getAndDecrement()
  3. Prefix increment(++x): incrementAndGet()
  4. Prefix decrement(--x): decrementAndGet()
  5. Assignment(=): set()

Solution Code

package com.example;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

public class Entry {

   static int counter;
   static AtomicInteger atomicCounter = new AtomicInteger();

   public static void main(String[] args) {
       //unsafe();
       safe();
   }

   private static void unsafe(){
       ExecutorService executorService = Executors.newCachedThreadPool();

       for (int i = 0; i < 5; i++){
           executorService.submit(() -> {
               System.out.println(counter++);
           });
       }

       try {
           executorService.awaitTermination(1, TimeUnit.SECONDS);
       } catch (InterruptedException e) {
           e.printStackTrace();
       } finally {
           executorService.shutdown();
       }
   }

   private static void safe(){
       ExecutorService executorService = Executors.newCachedThreadPool();

       for (int i = 0; i < 5; i++){
           executorService.submit(() -> {
               System.out.println(atomicCounter.getAndIncrement());
           });
       }

       try {
           executorService.awaitTermination(1, TimeUnit.SECONDS);
       } catch (InterruptedException e) {
           e.printStackTrace();
       } finally {
           executorService.shutdown();
       }
   }

}

Summary

We have learned how to use AtomicInteger. The full project code can be found here: https://github.com/dmitrilc/DaniWebAtomicInteger