Race Conditions

⭐ Interview Importance: MEDIUM
⏱️ Revision Time: 5 min

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 synchronized or Atomic classes.

Concept

Consider the statement count++. It looks like one operation, but at the CPU level, it is three separate operations:

  1. Read the current value of count from memory (e.g., 5).
  2. Increment the value locally (to 6).
  3. 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.