Race Conditions
TL;DR
- A Race Condition is a concurrency bug that occurs when two or more threads access shared data simultaneously, and the final result depends on the exact timing of their execution.
- It happens when an operation that appears to be single (like
count++) is actually composed of multiple CPU instructions. - Fixed by making the operation atomic using
synchronizedorAtomicclasses.
Concept
Consider the statement count++. It looks like one operation, but at the CPU level, it is three separate operations:
- Read the current value of
countfrom memory (e.g., 5). - Increment the value locally (to 6).
- Write the new value back to memory.
If Thread A and Thread B both execute count++ at the exact same millisecond:
- Thread A reads 5.
- Thread B reads 5.
- Thread A increments to 6 and writes 6 to memory.
- Thread B increments to 6 and writes 6 to memory.
The count is now 6, but it should be 7! One increment was completely lost because they “raced” to write the data.
Examples
public class RaceConditionExample {
// Shared variable
private static int count = 0;
public static void main(String[] args) throws InterruptedException {
Runnable incrementTask = () -> {
for (int i = 0; i < 10000; i++) {
count++; // NOT ATOMIC! Causes Race Condition
}
};
Thread t1 = new Thread(incrementTask);
Thread t2 = new Thread(incrementTask);
t1.start(); t2.start();
t1.join(); t2.join();
// Expected: 20000.
// Actual: Usually randomly between 10000 and 19000
System.out.println("Final Count: " + count);
}
}
Interview Questions
Q: What is a “Check-Then-Act” race condition?
A: It is a common pattern where you read a value, and based on that value, you take action. Example: if (map.containsKey(key) == false) { map.put(key, val); }.
Thread A checks and sees it is false. Thread B checks and sees it is false. Thread A puts the value. Thread B puts the value (overwriting A’s work).
To fix this, the entire block must be synchronized, or you must use concurrent structures like ConcurrentHashMap.putIfAbsent().
Q: How can you detect race conditions in Java?
A: Race conditions are notoriously difficult to detect because they are timing-dependent and might never happen on a developer’s local machine, only crashing in high-traffic production environments. You can use stress testing (spawning thousands of threads in a loop), or use advanced JVM tools like Thread Sanitizers or Java’s JCStress framework to explicitly hunt for interleaving issues.