Race condition in Java is a type of concurrency bug or issue which is introduced in your program because parallel execution of your program by multiple threads at same time, Since Java is a multi-threaded programming language hence risk of Race condition is higher in Java which demands clear understanding of what causes a race condition and how to avoid that. Anyway Race conditions are just one of hazards or riskpresented by use of multi-threading in Java just like deadlock in Java.
Race conditions occurs when two thread operate on same object without proper synchronization and there operation interleaves on each other. Classical
example of Race condition is incrementing a counter since increment is not an atomic operation and can be further divided into three steps like read, update and write. if two threads tries to increment count at same time and if they read same value because of interleaving of read operation of one thread to update operation of another thread, one count will be lost when one thread overwrite increment done by other thread. atomic operations are not subject to race conditions because those operation cannot be interleaved.
How to find Race Conditions in Java
Finding Race conditions in any language is most difficult job and Java is no different, though since readability of Java code is very good and synchronized constructs are well defined heaps to find race conditions by code review. finding race conditions by unit testing is not reliable due to random nature of race conditions. since race conditions surfaces only some time your unit test may passed without facing any race condition. only sure shot way to find race condition is reviewing code manually or using code review tools.
Code Example of Race Condition in Java
Based on my experience in Java synchronization and where we use synchronized keyword I found that two code patterns namely "check and act" and "read modify write" can suffer race condition if not synchronized properly. both cases rely on natural assumption that a single line of code will be atomic and execute in one shot which is wrong e.g. ++ is not atomic.
"Check and Act" race condition pattern
classical example of "check and act" race condition in Java is getInstance() method of Singleton Class, remember that was one questions which we have discussed on 10 Interview questions on Singleton pattern in Java as "How to write thread-safe Singleton in Java". getInstace() method first check for whether instance is null and than initialized the instance and return to caller. Whole purpose of Singleton is that getInstanceshould always return same instance of Singleton. if you call getInstance() method from two thread simultaneously its possible that while one thread is initializing singleton after null check, another thread sees value of _instance reference variable as null (quite possible in java) especially if your object takes longer time to initialize and enters into critical section which eventually results in getInstance() returning two separate instance of Singleton. This may not happen always because a fraction of delay may result in value of _instance updated in main memory. here is a code example
if(obj == null)
obj = new OnlyOne();
Good Codesynchronized(this)
{
if(obj == null)
obj = new OnlyOne();
}
read-modify-update race conditionsThis is another code pattern in Java which cause race condition, classical example is the non thread safe counter we discussed in
how to write thread safe class in Java. this is also a very popular multi-threading question where they ask you to find bugs on concurrent code. read-modify-update pattern also comes due to improper synchronization of
non-atomic operations or combination of two individual atomic operations which is not atomic together e.g. put if absent scenario. consider below code
counter++
this is not a atomic operation, it has 3 steps: 1. get the value 2. increase the value 3. assign to the value
Java Program to demonstrate Racing condition
package in.softcare;
public class ThreadDemo implements Runnable {
private int x = 0;
public int getValue() throws InterruptedException {
for (int i = 0; i < 8000; i++) {
x = i + 1;
if (x % 50 == 0)
Thread.sleep(2);
}
return x;
}
@Override
public void run() {
try {
System.out.println(Thread.currentThread().getName() + "-"
+ getValue());
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
package in.softcare;
public class ThreadTesting {
public static void main(String args[]) {
ThreadDemo t1 = new ThreadDemo();
Thread th1 = new Thread(t1);
Thread th2 = new Thread(t1);
Thread th3 = new Thread(t1);
Thread th4 = new Thread(t1);
th1.setName("First Thread");
th2.setName("Second Thread");
th3.setName("Third Thread");
th4.setName("Fourth Thread");
th1.start();
th2.start();
th3.start();
th4.start();
}
}
OUTPUT:
Fourth Thread-7850
First Thread-7950
Third Thread-7950
Second Thread-8000
Above Output is not constant always, it depends on OS and memory allocation of processor.