Atomicity of Concurrent Collection Operations
A concurrent collection can make each individual method call thread-safe without making a sequence of calls atomic. When correctness depends on several operations acting as one unit, the sequence needs explicit synchronization or an atomic collection operation designed for that invariant.
Thread safety of a collection method means that concurrent calls to that method do not corrupt the collection's internal state. It does not automatically make a larger algorithm composed of multiple calls indivisible.
A common example is check-then-act: `if (!map.containsKey(key)) { map.put(key, value); }`. Two threads can both observe that the key is absent before either performs `put`, so both proceed. The individual `containsKey` and `put` calls can be safe while the overall intent is not.
The fix is to use an operation whose semantics cover the whole compound action, such as `putIfAbsent`, `computeIfAbsent`, or `merge` on `ConcurrentHashMap`, when that operation matches the required invariant. Another option is external synchronization around the entire sequence when multiple structures or more complex conditions must change together.
The key design question is what must appear indivisible to other threads. If the invariant spans several collection operations, choosing a concurrent collection alone may be insufficient; the synchronization boundary must encompass the complete invariant-preserving operation.
This distinction also matters for compound reads. A sequence such as checking a size and then iterating, or reading one value and conditionally modifying another, may require stronger coordination than the individual collection methods provide.
Key points
- Thread-safe individual methods do not imply atomic compound operations.
- Check-then-act sequences can race between the check and the action.
- Prefer atomic APIs such as `putIfAbsent` or `computeIfAbsent` when their semantics match the requirement.
- Use external synchronization when an invariant spans multiple operations or multiple objects.
- Define atomicity around the application invariant, not merely around individual method calls.
Interview questions
Why is `containsKey` followed by `put` unsafe on a concurrent map?
The two method calls are separate operations, so another thread can run between them. Both threads can observe that the key is absent and then both perform the insertion. The collection can remain internally consistent while the application-level invariant is violated. An atomic method such as `putIfAbsent` expresses the intended operation as one concurrent-map action.
Does `ConcurrentHashMap` make every sequence of operations on the map atomic?
No. Its individual operations provide specified concurrency guarantees, and some compound operations are explicitly supported, but arbitrary sequences of calls are not automatically one atomic operation. For example, `get` followed by `put` can interleave with another thread. The solution is to use an appropriate atomic map method or synchronize the larger critical section.
When would you choose `computeIfAbsent` instead of `putIfAbsent`?
`computeIfAbsent` is useful when the value should be derived only when the key has no current mapping. The mapping function supplies that computation, and the map coordinates the conditional insertion. `putIfAbsent` is appropriate when the value is already available and should simply be installed if absent. The choice should follow the desired operation semantics rather than merely the method name.
How do you handle an invariant that spans multiple concurrent collections?
A single concurrent collection cannot make a cross-object invariant atomic. I would identify the complete state transition that must remain consistent and coordinate that transition with a shared lock or another appropriate concurrency protocol. If possible, I would redesign the state so the invariant is represented by one atomic data structure or operation. The important point is to place synchronization around the invariant rather than around arbitrary individual method calls.