April 10, 2024

Race Condition in Java With Examples

Race condition in Java may occur in a multi-threaded language like Java when two or more threads try to access a shared resource. If all the threads are just reading a shared object that poses no problem but modifying or writing a value may lead to incorrect results because of race condition.

In a multi-threaded environment, a thread after executing few steps may be preempted by another thread. That may leave the shared data in an inconsistent state. For example take the simple task of incrementing a counter– counter++;

This simple task of incrementing a counter is actually comprised of three steps-

  1. Read the value of counter variable.
  2. Increment the value by 1.
  3. Store the value of counter variable.

If there are two threads sharing this variable then the following scenario may happen-

int counter = 0;
counter = counter + 1; // Thread 1
counter = counter + 1; // Thread 2 started before thread 1 could save the new 
                      //value of counter, so Thread 2 also got the initial value of counter as 0.
store counter value // Thread 1
store counter value // Thread 2

So you end up with the counter value as 1 rather than the correct value 2 because of the interleaving threads. That’s what race condition can do to a shared object in a multi-threaded environment.

Error scenarios because of race condition

Because of the race condition, executing thread may read a stale value of shared object which may result in any of the following scenario.

  1. If a thread has to execute some logic based on the value of the variable. Because thread may end up reading a wrong value it may not act the way it was supposed to. This scenario is known as check-then-act race condition.
  2. A thread has to read the current value, modify it and store the new value. Again because of the race condition thread may end up reading and modifying a stale value. This scenario is known as read-modify-write race condition.

Example of race condition in Java

Here is a simple example where a shared integer variable is incremented and the value is displayed. Ten threads are created and each thread increments and then displays the value of the variable. Expected behavior is that each thread should get a unique value between 1-9.

public class RaceConditionDemo {
  int counter = 0;
  public  void incrementCounter(){
    try {
      Thread.sleep(100);
    } catch (InterruptedException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
    counter++;
  }
  public int getCounter(){
    return counter;
  }
  public static void main(String[] args) {
    RaceConditionDemo rc = new RaceConditionDemo();
    for(int i = 0; i < 10; i++){
      new Thread(new Runnable() {			
        @Override
        public void run() {
          rc.incrementCounter();
          System.out.println("value for " + Thread.currentThread().getName() + " - " + rc.getCounter());
        }
      }).start();
    }	
  }
}
Output
value for Thread-0 - 1
value for Thread-2 - 2
value for Thread-1 - 3
value for Thread-4 - 4
value for Thread-5 - 6
value for Thread-3 - 6
value for Thread-6 - 6
value for Thread-9 - 8
value for Thread-8 - 9
value for Thread-7 – 8

In one of the run the output came as above (note that the output may vary). As you can see Thread 5, 3 and 6 have got the same value 6, Thread 7 and 9 have also got the same value 8.

Avoiding race condition in Java

Now when you know what is race condition and seen an example too where interleaving threads read the same value of the shared object. That brings us to the question how to avoid race condition in Java.

It is clear that you need to restrict access to the critical section (code where shared resource is modified). In Java that’s what synchronized keyword does; synchronizes the access to the shared resource. Using synchronization ensures that the atomic operations are executed as a single operation without thread interference.

In the example showed above synchronizing the method call should avoid the race condition.

public class RaceConditionDemo {
  int counter = 0;
  public  void incrementCounter(){
    try {
      Thread.sleep(100);
    } catch (InterruptedException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
    counter++;
  }
  public int getCounter(){
    return counter;
  }
  public static void main(String[] args) {
    RaceConditionDemo rc = new RaceConditionDemo();
    for(int i = 0; i < 10; i++){
      new Thread(new Runnable() {			
        @Override
        public void run() {
          synchronized(rc){
            rc.incrementCounter();
            System.out.println("value for " + Thread.currentThread().getName() + " - " + rc.getCounter());
          }
        }
      }).start();
    }	
  }
}
Output
value for Thread-0 - 1
value for Thread-8 - 2
value for Thread-7 - 3
value for Thread-9 - 4
value for Thread-6 - 5
value for Thread-4 - 6
value for Thread-5 - 7
value for Thread-3 - 8
value for Thread-2 - 9
value for Thread-1 – 10

As you can see now every thread gets a unique value.

That's all for the topic Race Condition in Java With Examples. If something is missing or you have something to share about the topic please write a comment.


You may also like

No comments:

Post a Comment