Sharing primitives across threads in Java using atomic objects

Threading and parallel execution are popular choices when making applications more responsive and resource-efficient. Various tasks are carried out on separate threads where they either produce some result relevant to the main thread or just run in the background “unnoticed”. Often these tasks work autonomously meaning they have their own set of dependencies and variables. That is they do not interfere with a resource that is common to 2 or more threads.

However, that’s not always the case. Imagine that multiple threads are trying to update the same primitive like an integer counter. They perform some action and then update this counter. In this post we’ll see what can go wrong.

Here’s a service with methods to increment and decrement a counter. There’s also a method to get the current value of this counter:

public class SharedPrimitiveTesterService
{    
    private int counter = 0;
    
    public int increment()
    {
        counter++;
        return counter;
    }
    
    public int decrement()
    {
        counter--;
        return counter;
    } 
    
    public int getValue()
    {
        return counter;
    }
     
}

We also have two Callable objects that manipulate the counter in the service. The DecrementTask will attempt to decrement the counter and the IncrementTask will try the opposite:

import java.util.concurrent.Callable;

public class IncrementTask implements Callable<Integer>
{

    private final SharedPrimitiveTesterService sharedObjectService;
    private final int numberOfTimes;

    public IncrementTask(SharedPrimitiveTesterService sharedObjectService, int numberOfTimes)
    {
        this.sharedObjectService = sharedObjectService;
        this.numberOfTimes = numberOfTimes;
    }
    
    @Override
    public Integer call() throws Exception
    {
        for (int i = 0; i < numberOfTimes; i++)
        {
            sharedObjectService.increment();
        }
        return sharedObjectService.getValue();
    }

}
import java.util.concurrent.Callable;

public class DecrementTask implements Callable<Integer>
{
    private final SharedPrimitiveTesterService sharedObjectService;
    private final int numberOfTimes;

    public DecrementTask(SharedPrimitiveTesterService sharedObjectService, int numberOfTimes)
    {
        this.sharedObjectService = sharedObjectService;
        this.numberOfTimes = numberOfTimes;
    }
    
    @Override
    public Integer call() throws Exception
    {
        for (int i = 0; i < numberOfTimes; i++)
        {
            sharedObjectService.decrement();
        }
        return sharedObjectService.getValue();
    }
    
}

The implementation of the call method is simple to follow. They increment or decrement the counter of the service the given number of times and then return the final result.

The following code will try to increment the counter 1,000,000 times and decrement it 400,000 times on two separate threads. Therefore we’re expecting the end result to be 600,000, right? Let’s see what happens:

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

    SharedPrimitiveTesterService sharedObjectService = new SharedPrimitiveTesterService();
    Callable<Integer> incrementTask = new IncrementTask(sharedObjectService, 1000000);
    Callable<Integer> decrementTask = new DecrementTask(sharedObjectService, 400000);
    List<Callable<Integer>> calcTasks = new ArrayList<>();
    calcTasks.add(decrementTask);
    calcTasks.add(incrementTask);

    try
    {
        List<Future<Integer>> futures = newCachedThreadPool.invokeAll(calcTasks);
        for (Future<Integer> future : futures)
        {
            future.get();
        }
        int res = sharedObjectService.getValue();
        System.out.println(res);
    } catch (InterruptedException | ExecutionException ex)
    {
        System.out.println(ex.getMessage());
    }
}

We call the get method of the Future objects to make sure that the callables have completed. You should see that the end result “res” will be close to 600,000 but is not quite there. It can be 601,530 or 602,322. Occasionally the end result might even be exactly 600,000 if you get lucky.

The above problem is a classic resource sharing example in parallel computing. Two or more threads are trying to update the same primitive and some updates are lost. The problem is that increasing or decreasing an integer is not an atomic operation in Java – or in any other popular object-oriented language out there in fact. Adding an integer to another integer requires 3 instructions to the relevant section of the CPU: retrieve the current value of the variable, add the incoming value to it, assign the new value to the variable. With so many updates like in our example it is possible that a decrement and an increment operation produce these operations at the same time causing them to “intermingle”.

Luckily for us this is not a new problem and the Java Concurrency API has an easy solution to the problem. The java.util.concurrent.atomic package includes a number of objects whose names start with “Atomic” such as AtomicBoolean. They include AtomicInteger which is exactly what we need. The available methods, which are listed in the referenced documentation, will allow you to increment and decrement its value in an atomic way so that those basic instructions to the CPU will be shielded from other threads while they are being completed by a given thread.

Here’s our revised SharedPrimitiveTesterService:

import java.util.concurrent.atomic.AtomicInteger;

public class SharedPrimitiveTesterService
{
    private final AtomicInteger sharedCounter = new AtomicInteger(0);

    public int increment()
    {        
        return sharedCounter.incrementAndGet();
    }   
    
    public int decrement()
    {
        return sharedCounter.decrementAndGet();
    }
    
    public int getValue()
    {
        return sharedCounter.get();
    }
}

There’s no change required for the other parts of the example code. Run the code now and the final result should always be 600,000.

View all posts related to Java here.

Advertisement

About Andras Nemes
I'm a .NET/Java developer living and working in Stockholm, Sweden.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

Elliot Balynn's Blog

A directory of wonderful thoughts

Software Engineering

Web development

Disparate Opinions

Various tidbits

chsakell's Blog

WEB APPLICATION DEVELOPMENT TUTORIALS WITH OPEN-SOURCE PROJECTS

Once Upon a Camayoc

Bite-size insight on Cyber Security for the not too technical.

%d bloggers like this: