Explain how you would get thread-safety issues due to non-atomic operations with a code example

The code snippets below demonstrates non-atomic operations producing incorrect results with code. The program below uses a shared Counter object, that is shared between three concurrent users (i.e. three threads). The Counter object is responsible for incrementing the counter.

Firstly, the Counter class. The counted values are stored in a HashMap by name (i.e. thread name) as the key for later retrieval.

import java.util.HashMap;

import java.util.Map;

 

public class Counter {

 

//         shared variable or resource

private Integer count = Integer.valueOf(0);

private Map<String, Integer> userToNumber = new HashMap<String, Integer>(10);

 

public void  increment() {

try {

count = count + 1;      //          increment the counter

Thread.sleep(50);      //          to imitate other operations and

//          to make the racing condion to occur more often for the demo

Thread thread = Thread.currentThread();

userToNumber.put(thread.getName(), count);

}

catch (InterruptedException e) {

e.printStackTrace();

}

}

public Integer getCount(String name) {

return userToNumber.get(name);

}

}

Next, the Runnable task where each thread will be entering and executing concurrently.

public class CountingTask implements Runnable

{

private Counter counter;

public CountingTask(Counter counter) {

super();

this.counter = counter;

}

 

public void run() {

counter.increment();

Thread thread = Thread.currentThread();

System.out.println(thread.getName() + ” value is ” + counter.getCount(thread.getName()));

}

}

Finally, the Manager class that creates 3 new threads from the main thread.

public class CountingManager {

 

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

{

 

Counter counter = new Counter(); // create an instance of the Counter

CountingTask task = new CountingTask(counter); // pass the counter to the runnable CountingTask

 

//Create 10 user threads (non-daemon) from the main thread that share the counter object

Thread thread1 = new Thread(task, “User-1”);

Thread thread2 = new Thread(task, “User-2”);

Thread thread3 = new Thread(task, “User-3”);

 

//start the threads

thread1.start();

thread2.start();

thread3.start();

 

//observe the racing conditions in the output

}

}

To see the racing condition, inspect the output of the above code:

User-3 value is 3

User-1 value is 3

User-2 value is 3

All three threads or users get assigned the same value of 3 due to racing conditions. We are expecting to see three different count values to be assigned from 1 to 3. What happened here is that when the first thread incremented the count from 0 to 1 and entered into the sleep(50) block, the second and third threads incremented the counts from 1 to 2 and 2 to 3 respectively.

This shows that the 2 operations — the operation that increments the thread and the operation that stores the incremented value in a HashMap are not atomic, and produces incorrect results due to racing conditions.