Tuesday, 30 June 2015

Race condition in Java multi-threading

Race condition occurs in a multi-threaded environment when more than one thread try to access a shared resource (modify, write) at the same time. Note that it is safe if multiple threads are trying to read a shared resource as long as they are not trying to change it. Since multiple threads try to race each other to finish executing a method thus the name race condition.

Note that multiple threads executing inside a method is not a problem in itself, problem arises when they try to access the same resource. Examples of shared resources are class variables, DB record in a table, writing in a file.

Let’s see one example where we have a shared instance variable, as we have three threads sharing the same object of the class.

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.

Example of race condition

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() {
        //System.out.println("in getValue method ");
        
        return c;
    }
    
    @Override
    public void run() {
        this.increment();
        System.out.println("Value for Thread After increment " + Thread.currentThread().getName() + " " + this.getValue());
        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.

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.

//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() {
        //System.out.println("in getValue method ");
        
        return c;
    }
    
    @Override
    public void run() {
        synchronized(this){
            this.increment();
            System.out.println("Value for Thread After increment " + Thread.currentThread().getName() + " " + this.getValue());
            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. Synchronization in Java multithreading
  2. What if run() method called directly instead of start() method
  3. isAlive() & join() methods in Java multithreading
  4. Why wait(), notify() and notifyAll() methods are in Object class
  5. Deadlock in Java multi-threading
  6. Java Multi-threading interview questions

You may also like -

2 comments:

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

    ReplyDelete