Concurrency Fundamentals • Race Conditions & Critical SectionsMedium⏱️ ~2 min
Common Race Condition Patterns
Key Insight
Most race conditions follow a few common patterns. Recognizing them helps you spot bugs before they happen.
Common Race Condition Patterns
Pattern 1: Check-Then-Act
You check a condition, then act on it. But the condition might change between check and act.
if (file.exists()) {
// File deleted here!
file.read(); // CRASH
}
// File deleted here!
file.read(); // CRASH
}
Pattern 2: Read-Modify-Write
You read a value, modify it, write it back. Another thread does the same between your read and write.
counter++ // Read, Add, Write
// Both read 5, both write 6
// Both read 5, both write 6
Pattern 3: Compound Operations
Multiple operations that should be atomic but are not. HashMap put-if-absent is a classic example.
Pattern 4: Lazy Initialization
Creating a singleton on first use. Two threads might both see null and create two instances.
Prevention: Use atomic operations, proper locking, or thread-safe data structures. The fix depends on the pattern.
💡 Key Takeaways
✓Check then act: condition may change between check and action. Use atomic operations or hold lock during both.
✓Read modify write: interleaving causes lost updates. Use atomic operations (compareAndSwap) or locks.
✓Compound actions: multiple operations must complete atomically. Wrap entire sequence in a lock.
✓Lazy initialization: multiple threads may create duplicate instances. Use double checked locking or static initialization.
✓Time of check to time of use (TOCTOU): file system races where file state changes between check and use.
📌 Examples
1Lazy singleton: if (instance == null) { instance = new Singleton(); }. Two threads can both see null and create two instances.
2putIfAbsent: if (!map.containsKey(k)) { map.put(k, v); }. Another thread may insert between check and put.