Data Race in Java
A data race occurs when multiple threads access the same mutable memory concurrently, at least one access is a write, and the accesses are not ordered by appropriate synchronization. In Java, synchronization, locks, or suitable concurrent utilities establish the ordering needed for safe access.
A data race requires conflicting accesses to shared state: two threads access the same variable or memory location, at least one access writes, and there is no sufficient synchronization establishing a happens-before relationship between the accesses. The result is not merely that operations happen in an inconvenient order; the program lacks the required memory-ordering guarantees.
In Java, `synchronized` is a common way to prevent data races. Entering and leaving a monitor establishes synchronization between corresponding monitor operations, so changes made while holding the same lock can become visible to another thread that subsequently acquires that lock.
A `volatile` field can provide visibility and ordering guarantees for individual reads and writes, but it does not make compound operations such as `count++` atomic. For counters and other compound state changes, `AtomicInteger`, locks, or another appropriate concurrency mechanism may be needed.
A race condition is a broader term than data race. A program can have a race condition because correctness depends on the relative timing of operations even when every individual memory access is properly synchronized. A data race specifically concerns conflicting unsynchronized memory accesses under the language's concurrency model.
Key points
- A data race involves concurrent conflicting accesses to shared memory with at least one write.
- Proper synchronization establishes ordering, commonly through `synchronized`, locks, or concurrency utilities.
- `volatile` provides visibility and ordering for a variable but does not make compound operations atomic.
- `count++` is a read-modify-write operation and is unsafe for shared counters without suitable synchronization.
- Data race and race condition are related but not interchangeable terms.
Interview questions
How does `synchronized` prevent a data race?
`synchronized` provides mutual exclusion for code protected by the same monitor, so only one thread at a time can execute the protected critical section. It also establishes happens-before relationships between monitor release and a subsequent acquisition of that monitor. Together, these properties protect the critical section and provide the required visibility and ordering guarantees.
What is a data race in Java?
A data race occurs when threads access the same shared memory concurrently, the accesses conflict, and there is no synchronization that orders them. At least one of the conflicting accesses must be a write. This can make program behavior unreliable because the Java Memory Model does not provide the required ordering for those accesses.
Why is `count++` unsafe when multiple threads update a shared `int`?
`count++` is not one indivisible operation; it reads the current value, adds one, and writes the result. Two threads can read the same old value and then overwrite each other's updates. The operation therefore needs synchronization or an atomic counter when the variable is shared between threads.
What does `volatile` guarantee, and why is it not a replacement for `synchronized`?
`volatile` makes reads and writes of that variable participate in the Java Memory Model's visibility and ordering rules. It does not provide mutual exclusion, so multiple threads can still execute a compound operation concurrently. Consequently, `volatile` is appropriate for some state flags but is insufficient by itself for operations such as incrementing a shared counter.
Is every race condition a data race?
No. A race condition is a broader correctness problem caused by dependence on the relative timing or ordering of operations. A data race specifically concerns conflicting memory accesses that lack the required synchronization. For example, two correctly synchronized operations can still produce an incorrect result if the application requires a particular ordering that the synchronization does not enforce.