Race condition in Java is a type of concurrency bug or issue that is introduced in your program because of parallel execution of your program by multiple threads at the same time, Since Java is a multi-threaded programming language hence the risk of Race condition is higher in Java which demands a clear understanding of what causes a race condition and how to avoid that. Anyway, Race conditions are just one of the hazards or risks presented by the use of multi-threading in Java just like deadlock in Java. Race conditions occur when two threads operate on the same object without proper synchronization and their operation interleaves on each other.
The Classical example of the Race condition is incrementing a counter since increment is not an atomic operation and can be further divided into three steps like reading, update, and write.
If two threads try to increment count at the same time and if they read the same value because of interleaving of a read operation of one thread to update operation of another thread, one count will be lost when one thread overwrite increment done by another thread. atomic operations are not subject to race conditions because those operations cannot be interleaved.
This is also a popular multi-threading interview question during core java interviews. In this article, we will see how to find race condition in Java and two sample code patterns which often causes race conditions in Java.
The Classical example of the Race condition is incrementing a counter since increment is not an atomic operation and can be further divided into three steps like reading, update, and write.
If two threads try to increment count at the same time and if they read the same value because of interleaving of a read operation of one thread to update operation of another thread, one count will be lost when one thread overwrite increment done by another thread. atomic operations are not subject to race conditions because those operations cannot be interleaved.
This is also a popular multi-threading interview question during core java interviews. In this article, we will see how to find race condition in Java and two sample code patterns which often causes race conditions in Java.
And, if you are serious about learning Multithreading in Java then you can also check out these advanced Mulitthreaidng and concurrency courses to become an expert in Multithreading, concurrency, and Parallel programming in Java with a strong emphasis on high performance.
How to find Race Conditions in Java
Finding Race conditions in any language is the most difficult job and Java is no different, though since the 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 the random nature of race conditions.
Since race conditions surface only some time your unit test may be passed without facing any race condition.
The only sure-shot way to find race conditions is by reviewing code manually or using code review tools that can alert you on potential race conditions based on code pattern and the use of synchronization in Java. I solely rely on code review and have yet to find a suitable tool for exposing race conditions in java.
Since race conditions surface only some time your unit test may be passed without facing any race condition.
The only sure-shot way to find race conditions is by reviewing code manually or using code review tools that can alert you on potential race conditions based on code pattern and the use of synchronization in Java. I solely rely on code review and have yet to find a suitable tool for exposing race conditions in java.
Code Example of Race Condition in Java
Based on my experience in Java synchronization and where we use synchronized keywords I found that two code patterns namely "check and act" and "read modify write" can suffer race conditions if not synchronized properly. both cases rely on the natural assumption that a single line of code will be atomic and execute in one shot which is wrong e.g. ++ is not atomic.
1. "Check and Act" race condition pattern
a classical example of the "check and act" race condition in Java is the getInstance() method of Singleton Class, remember that was one question 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 the instance is null and then initialized the instance and return to the caller.
The whole purpose of Singleton is that getInstance should always return the same instance of Singleton.
If you call the getInstance() method from two threads simultaneously its possible that while one thread is initializing singleton after null check, another thread sees the value of _instance reference variable as null (quite possible in java) especially if your object takes a longer time to initialize and enters into a critical section which eventually results in getInstance() returning two separate instances of Singleton.
This may not happen always because a fraction of delay may result in the value of _instance updated in main memory. here is a code example
The whole purpose of Singleton is that getInstance should always return the same instance of Singleton.
If you call the getInstance() method from two threads simultaneously its possible that while one thread is initializing singleton after null check, another thread sees the value of _instance reference variable as null (quite possible in java) especially if your object takes a longer time to initialize and enters into a critical section which eventually results in getInstance() returning two separate instances of Singleton.
This may not happen always because a fraction of delay may result in the value of _instance updated in main memory. here is a code example
public Singleton getInstance(){
if(_instance == null){ //race condition if two threads sees _instance= null
_instance = new Singleton();
}
}
an easy way to fix "check and act" race conditions is to synchronized keyword and enforce locking which will make this operation atomic and guarantees that block or method will only be executed by one thread and result of the operation will be visible to all threads once synchronized blocks completed or thread exited form synchronized block.
2. read-modify-update race conditions
This is another code pattern in Java which cause race condition, classical example is the non-thread-safe counter we discussed in how to write a thread-safe class in Java. this is also a very popular multi-threading question where they ask you to find bugs on concurrent code.
The read-modify-update pattern also comes due to improper synchronization of non-atomic operations or a combination of two individual atomic operations which is not atomic together e.g. put if absent scenario. consider below code
The read-modify-update pattern also comes due to improper synchronization of non-atomic operations or a combination of two individual atomic operations which is not atomic together e.g. put if absent scenario. consider below code
if(!hashtable.contains(key)){
hashtable.put(key,value);
}
here we only insert an object into hashtable if it's not already there. point is both contains() and put() are atomic but still, this code can result in race condition since both operation together is not atomic.
Consider thread T1 checks for conditions and goes inside if block now CPU is switched from T1 to thread T2 which also checks the condition and goes inside if block. now we have two threads inside if block which results in either T1 overwriting T2 value or vice-versa based on which thread has CPU for execution.
In order to fix this race condition in Java, you need to wrap this code inside the synchronized block which makes them atomic together because no thread can go inside the synchronized block if one thread is already there.
Consider thread T1 checks for conditions and goes inside if block now CPU is switched from T1 to thread T2 which also checks the condition and goes inside if block. now we have two threads inside if block which results in either T1 overwriting T2 value or vice-versa based on which thread has CPU for execution.
In order to fix this race condition in Java, you need to wrap this code inside the synchronized block which makes them atomic together because no thread can go inside the synchronized block if one thread is already there.
These are just some of the examples of race conditions in Java, there will be numerous based on your business logic and code. the best approach to find Race conditions is code review but it's hard because thinking concurrently is not natural and we still assume code to run sequentially.
Problems can become worse if JVM reorders code is absent of proper synchronization to gain a performance benefit and this usually happens on production under heavy load, which is worst. I also suggest doing load testing in a production-like environment which many times helps to expose race conditions in java. Please share if you have faced any race conditions in java projects.
Problems can become worse if JVM reorders code is absent of proper synchronization to gain a performance benefit and this usually happens on production under heavy load, which is worst. I also suggest doing load testing in a production-like environment which many times helps to expose race conditions in java. Please share if you have faced any race conditions in java projects.
Other Java Thread Tutorial you may like
And lastly, let me know if you have been asked this question before? or what is your favorite Java multithreading interview question?
I have a question regarding the below example of not being thread safe .
ReplyDeleteAll the operations on hashtable are syncronized right ? then where is the question of mutli threads acting on this datastructure at a time ?
if(!hashtable.contains(key)){
hashtable.put(key,value);
}
On these lines below:
ReplyDelete1. if (! hashtable.contains(key)){
2. hashtable.put(key,value);
3. }
Say that two threads A and B both run line 1 before the two of them reach line 2. This is possible even though the Java hashtable is thread-safe. In this case, both threads will also run line 2 at any order depending on how the system runs at that time and one put will over-write the value of the other put.
In this case, the whole if block should be synchronized to solve the race-condition, like below:
synchronized {
if( ! hashtable.contains(key)){
hashtable.put(key,value);
}
}
@Sindhu: When we say hashtable is synchronized, it means methods of hashtable are synchronized. Here both 'contains(key)' and 'put(key, value)' are synchronized. But the code using them here is not synchronized.
ReplyDeleteSynchronized Block should be on Specific Object reference.
ReplyDeletesynchronized(this) {
if( ! hashtable.contains(key)){
hashtable.put(key,value);
}
}
As you said, values can be override while both threads will run the put() statement, but since the values will be unique ie key/value pair so in my opinion things can also work fine if we do not synchronize the operation.Kindly share your opinion on this.
ReplyDeleteIn the code for hashTable the condition is, if the key is not there already then store the key/value pair. So in that case 2 interleaving threads may both put the different values with the same key... Though the condition there is to stop that from happening.
ReplyDeleteNice explanation ,thats why i always prefer javarevisited
ReplyDeleteIt's nice explanation, as usual, but this URL is missing here
ReplyDeletehttps://javarevisited.blogspot.com/2011/07/java-multi-threading-interview.html
Sorry, what do you mean by missing? is the link is broken?
ReplyDeleteThis is a great blog on race conditions, would have been outstanding if you explain how to use to wrap code inside the synchronized block which makes them atomic together because no thread can go inside the synchronized block if one thread is already there with an example.
ReplyDelete