Thursday, September 2, 2021

Race Condition in Java Multi-Threading

Race condition in Java occurs in a multi-threaded environment when more than one thread try to access a shared resource (modify, write) at the same time. Since multiple threads try to race each other to finish executing a method thus the name race condition.

Two points to note about race condition are-
  • It is safe if multiple threads are trying to read a shared resource as long as they are not trying to change it.
  • Multiple threads executing inside a method is not a problem in itself, problem arises when these threads try to access the same resource. Examples of shared resources are class variables, DB record in a table, writing in a file.

Example of race condition in Java

Let’s see one example of race condition in Java multi-threading, where we have a shared instance variable. Since there are three threads sharing the same object of the class so the field in the object is shared among the threads.

This instance variable c is incremented and decremented so every thread should leave it in the state it initially was i.e. if c is zero in the start, incrementing it will make it 1 and decrementing it will make it zero again.

Here some delay is simulated using sleep(), as in a real production system there may be many processes running and many users might be accessing the same application at any given time. In that kind of scenario we can’t be sure of when context switching will happen among the threads contending for CPU-cycle. That’s why race condition related bugs are very difficult to find and you may not be even able to reproduce them as that kind of context-switching may not happen when you are trying to reproduce the race condition related error.

Java code

class Counter  implements Runnable{
  private int c = 0;

  public void increment() {
    try {
      Thread.sleep(10);
    } catch (InterruptedException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
    c++;
  }

  public void decrement() {    
    c--;
  }

  public int getValue() {
    return c;
  }
    
  @Override
  public void run() {
    //incrementing
    this.increment();
    System.out.println("Value for Thread After increment " 
    + Thread.currentThread().getName() + " " + this.getValue());
    //decrementing
    this.decrement();
    System.out.println("Value for Thread at last " 
    + Thread.currentThread().getName() + " " + this.getValue());        
  }
}

public class RaceConditionDemo{
  public static void main(String[] args) {
    Counter counter = new Counter();
    Thread t1 = new Thread(counter, "Thread-1");
    Thread t2 = new Thread(counter, "Thread-2");
    Thread t3 = new Thread(counter, "Thread-3");
    t1.start();
    t2.start();
    t3.start();
  }    
}

Output

Value for Thread After increment Thread-2 3
Value for Thread at last Thread-2 2
Value for Thread After increment Thread-1 2
Value for Thread at last Thread-1 1
Value for Thread After increment Thread-3 1
Value for Thread at last Thread-3 0

It can be seen how the shared variable c is giving wrong values.

Using synchronization to avoid race condition in Java

To fix the race condition we need to have a way to restrict resource access to only one thread at a time. We have to use synchronized keyword to synchronize the access to the shared resource. Let’s see the same example again with proper synchronization to avoid race condition.

Avoiding race condition Java example

//This class' shared object will be accessed by threads
class Counter  implements Runnable{
  private int c = 0;

  public  void increment() {
    try {
      Thread.sleep(10);
    } catch (InterruptedException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
    c++;
  }

  public  void decrement() {    
    c--;        
  }

  public  int getValue() {
    return c;
  }
    
  @Override
  public void run() {
    synchronized(this){
      // incrementing
      this.increment();
      System.out.println("Value for Thread After increment " 
       + Thread.currentThread().getName() + " " + this.getValue());
      //decrementing
      this.decrement();
      System.out.println("Value for Thread at last " + Thread.currentThread().getName() 
          + " " + this.getValue());
    }        
  }
}

public class RaceConditionDemo{
  public static void main(String[] args) {
    Counter counter = new Counter();
    Thread t1 = new Thread(counter, "Thread-1");
    Thread t2 = new Thread(counter, "Thread-2");
    Thread t3 = new Thread(counter, "Thread-3");
    t1.start();
    t2.start();
    t3.start();
  }    
}

Output

Value for Thread After increment Thread-2 1
Value for Thread at last Thread-2 0
Value for Thread After increment Thread-3 1
Value for Thread at last Thread-3 0
Value for Thread After increment Thread-1 1
Value for Thread at last Thread-1 0

It can be seen from the output how threads are accessing the shared resource one at a time now. Synchronizing the access with in the run() method made it happen.

Points to note-

  • A code block that has a shared resource and may lead to race conditions is called a critical section.
  • Race conditions can be avoided by proper thread synchronization in critical sections.

That's all for this topic Race Condition in Java Multi-Threading. If you have any doubt or any suggestions to make please drop a comment. Thanks!


Related Topics

  1. Deadlock in Java Multi-Threading
  2. Synchronization in Java - Synchronized Method And Block
  3. Thread Starvation in Java Multi-Threading
  4. What if run() Method Called Directly Instead of start() Method - Java Multi-Threading
  5. Java Multithreading Interview Questions And Answers

You may also like-

  1. Lambda Expressions in Java 8
  2. Java ReentrantLock With Examples
  3. How HashMap Internally Works in Java
  4. final Vs finally Vs finalize in Java
  5. Try-With-Resources in Java With Examples
  6. static Import in Java With Examples
  7. Difference Between Abstract Class And Interface in Java
  8. Varargs (Variable-length Arguments) in Java

4 comments:

  1. Hey really great article on Multi threading very good example.

    ReplyDelete
  2. Your blog is awesome. Thanks for sharing information on threading so generously.

    ReplyDelete
  3. The example runs correctly but the class is not thread safe. With increment() and decrement() methods public, multiple threads could access these methods without any synchronisation.

    ReplyDelete