Race condition is one of the famous concurrency problems that every developer encounters when working in concurrent programming. Race condition may lead to inconsistent state of the system. The article explains race condition and its types with examples, root causes and best practices to avoid race condition.
Race condition is a situation when multiple threads races to operate(modify) on the shared resource, in a sequence dependent manner.
Sounds complex! Have patience. We will look into typical race condition examples in detail.
Race Condition Types
Most race condition problems can be divided into two broad categories.
Check-then-act concurrency problem
You are planning to go for a movie at 5 pm. You are inquiring about availability of the tickets at 4 pm. Representative says its available. You relax and reach to the ticket window 5 minutes before the show. And you can guess what. Its house full. The problem here was in the duration between check and act. You inquired at 4 and acted at 5. In the meantime, someone else grabbed the tickets. That’s race condition – specifically check-then-act scenario of race condition.
Check-then-act represents a situation where multiple threads checks for some condition to be true. Based on the out-come, they try to change state of the variable. By the time thread “checks” and then “acts”, the condition may have been invalidated by some other threads.
Consider example where multiple threads try to insert elements to a bounded collection. The figure on left shows that there is an invariant condition(should always hold true) on list. Size of the list cannot exceed MAX_SIZE. The current value of list is 9. Two threads check for the condition one after other. Both thinks that they can insert an element. Now eventually both threads are going to add element to the list, causing list size to exceed MAX_SIZE. By the time one thread performs “check for the condition” and then “act – insert element”, some other thread has already acted – inserted an element.
Here correctness of the program depends on luck. Timings of the executing the statements. The same program will give correct result if “check” operation was performed after “add to list” operation by Thread 2.
[java title=”Bounded List – Check-then-act Race Condition”]
public static void addNumber(){
if(numbers.size()<MAX_SIZE){ // Check
numbers.add(new Random().nextInt()); // Act
}
}
[/java]
Other similar check-then-act problems can be seen in poorly created Singletons. Where public factory method checks for instance variable to be null. If null, the instance of the singleton is created. Both of these examples are runnable and testable in the attached zip.
[java title=”Poorly created singleton – Check-then-act Race Condition”]
public static PoorSingleton getInstance(){
if (instance == null) // Check
{
instance = new PoorSingleton(); // Act
}
return instance;
}
[/java]
Read-modify-write concurrency problem
Read-modify-write goes like this for Thread 1. “1 – Read a variable. 2 – Operate on it. 3 – Write it back to the variable.” Here by the time thread executes 2 and 3, some other thread may change state of the variable. This effectively invalidates the statement 1 executed by Thread 1. So Thread 1 ends up operating on stale value of the variable.
The typical example can be seen when counter is used as an instance variable in a multi-threaded environment like in servlet. When serving the request you are just doing, innocent looking, increment operation. This increment operation may look like a single atomic statement. However, at run-time, it is broken down into machine level instructions which reads the variable, performs increment and assigns incremented, temporary value, back to the count variable. This sequence of execution may be interleaved by different threads in a way that produces incorrect result.
This read-modify-write problem arises when a thread is changing value of a variable based on its previous value. The assumption is made that no one else changed the value by the time the previous value was read and new value was set. This may not hold true in concurrent environment.
Read-modify-write problems often results into a “lost-update” problem where modification done by one/some threads is lost. In the example, if the counter value was 0, it should produce the result 2 but the update of 1 thread is lost.
[java title=”Counter – Read-modify-write Race Condition”]
public static void countUp(){
count++; // Read(count variable) – Modify(increment) – Write(assi gn count variable)
}
[/java]
Root Causes Of Race Condition
Let us try to find what is common in above problems. In all of them, we have shared data which is worked upon by multiple threads. If the boundedList was confined to a single thread – like a method level variable, each thread will have its own copy. When it is shared, if we could have ensured that only Thread 1 will make modifications to the list.
- Shared data for multiple threads
- Mutation of the shared data by multiple threads
Best Practices
When we know the root causes of the problem, it is easy to take precautions. Here are some points which can help to avoid race condition.
- Create stateless classes
- Use immutable objects
- Side-effect free functions
Above three is not always possible. So consider below points.
- Use atomic classes in java.util.concurrent.atomic package
- If mutation is required, dedicate one thread for doing mutation(changing values of variable). This is also known as actor-based concurrency
- Caution – Non synchronized collections are ALWAYS prone to race conditions. Use concurrent versions of those collections. e.g. prefer ConcurrentHashMap over HashMap
- Use appropriate synchronization in below sequence of preference
- Reentrant read-write lock
- Synchronized block
- Synchronized method
- Sincere code reviews
- Testing in multi-threaded environment
- Follow all above religiously
Runnable Code Example
Download the zip file at below link which also contains TestNG unit tests using multi-threaded testing to reproduce the race condition problem.
Have got some suggestions, feedback or discussion points? Feel free to write back and comment.
September 25, 2016, 5:47 pm
Thank you. Nice explanation!