Atomicity in Java
Atomicity determines whether an operation appears indivisible to other threads: observers cannot see a partially completed atomic operation. In Java, compound actions such as `count++` are not automatically atomic just because the variable is shared or declared `volatile`.
An atomic operation completes as one indivisible unit from the perspective of other threads. For a shared counter, this means another thread cannot interleave its own update between the read and write that make up one logical increment.
Many Java expressions are actually multiple actions. For example, `count++` reads `count`, adds one, and writes the result back. Two threads can both read the same old value and then both write the same new value, causing a lost update.
The Java concurrency APIs provide atomic classes such as `AtomicInteger` and `AtomicLong` for common single-variable atomic operations. Methods such as `incrementAndGet()` perform the update atomically and can be used without an explicit lock for that operation.
Atomicity is different from visibility and ordering. `volatile` can provide visibility and ordering guarantees for accesses to a variable, but it does not turn a compound read-modify-write operation such as `count++` into an atomic operation.
For larger critical sections involving multiple variables or multiple related operations, use synchronization or another appropriate locking mechanism. Atomic classes are particularly useful when the required state transition can be expressed as a supported atomic operation.
Key points
- `count++` is a compound read-modify-write operation and is not atomic.
- `AtomicInteger.incrementAndGet()` provides an atomic increment.
- `volatile` does not make compound operations such as `++` atomic.
- Atomicity, visibility, and ordering are distinct concurrency properties.
- Use locks when an invariant spans multiple operations or shared variables.
Interview questions
Why can two threads both increment a counter and still produce too few increments?
A non-atomic increment can allow both threads to read the same old value before either writes its result. Each thread then writes the same incremented value, so one update effectively overwrites the other. This is a classic lost-update race. Making the read-modify-write operation atomic prevents that interleaving for the protected operation.
Is `count++` atomic in Java? Why or why not?
No, `count++` is not atomic for a shared variable. It involves reading the current value, adding one, and writing the result, so another thread can interleave between those steps. Two threads can therefore read the same value and overwrite each other's increments. An `AtomicInteger` or appropriate synchronization can prevent this lost-update problem.
Does `volatile` make a counter increment atomic?
No. `volatile` gives a thread visibility and ordering guarantees for accesses to that variable, but `count++` still consists of a read followed by a write. Another thread can modify the value between those actions. For an atomic increment, use an `AtomicInteger` operation or synchronization.
How would you make a shared counter thread-safe?
For a simple counter, I would typically use `AtomicInteger` and call `incrementAndGet()` or another suitable atomic operation. If the counter update is part of a larger critical section, I would consider `synchronized` or an explicit lock so the whole invariant is protected. The choice depends on whether the state transition involves only one independently updateable value or several related pieces of state.