Skip to main content

Java Concurrency - Race Conditions - Read-Modify-Write

Greetings!

When two or more threads access shared data at the same time race condition occurred. These operation should be atomic to thread-safe and we call them "compound actions".

  • read-modify-write - read a value, modify it, write back (ex: increment a value).
  • check-then-act - check a condition and act accordingly; common situation is lazy initialization (ex: singleton pattern).

In this blog post we are going to examine read-modify-write race condition.

Let's say we need a counter and we designed below class.


public class Counter {

    private int count = 0;

    public int getAndIncrement() {
        return count++;
    }

    public int get() {
        return count;
    }

}


Can you spot a possible bug here? Well, for a single threaded application this is working fine. But for a multi-threaded application this will give unexpected results.

Problem here is count++ is not an atomic operation.

Let's create few threads and see the results.


import java.util.stream.IntStream;

public class CounterApp {

    public static void main(String[] args) throws InterruptedException {
        Counter counter = new Counter();

        Thread thread1 = new Thread(() -> IntStream.range(0, 100)
                                                    .forEach(i -> counter.getAndIncrement()));
        Thread thread2 = new Thread(() -> IntStream.range(0, 100)
                                                    .forEach(i -> counter.getAndIncrement()));

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

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

        System.out.println("Count: " + counter.get());
    }

}


Each thread increment 100 times, so I expected 200 as result. Surprise!!! This gave me results like 155, 200, 140, 196, etc. In each run it gives different results.
(If you are always getting 200, try increasing to a higher value)

what is happening here is both threads read and update (write) the counter at the same time but the changes are not visible.

We can fix this using synchronized.


public class Counter {

    private int count = 0;

    public synchronized int getAndIncrement() {
        return count++;
    }

    public synchronized int get() {
        return count;
    }

}



Comments