A get/put sequence retrieves a mapping value, decides whether to create or update it, and then stores the result. It is a useful fallback when computeIfAbsent() would recursively modify the same map, but it is not atomic when multiple threads can update a concurrent map. section 8.12 section 9.12
Caching a recursively computed value
For a cache whose calculation calls the cache again, use a direct sequence: get the cached result; if absent, calculate it; put the result; return it. This avoids the ConcurrentModificationException that can arise when computeIfAbsent() indirectly modifies the same map. The sequence remains sensitive to call order in ordinary caching code, so tests should not rely on one traversal order. section 8.12
The concurrent-update trap
On a ConcurrentMap, reading a count and then writing the incremented count allows another thread to change the entry between calls, losing an update. Use merge(key, 1, Integer::sum) for this counting operation instead. section 9.12
Set<String> transitiveReferences(String variable) {
Set<String> result = refCache.get(variable);
if (result == null) {
result = calc(variable);
refCache.put(variable, result);
}
return result;
}
Shows the direct get/put cache sequence used instead of recursive computeIfAbsent(). section 8.12
void addValue(ConcurrentMap<String, Integer> map, String key) {
map.merge(key, 1, Integer::sum);
}
Shows merge() replacing a non-atomic concurrent counting sequence. section 9.12
Avoid the pattern’s common failure modes:
- Use get/put when a computeIfAbsent() function directly or indirectly modifies the same map. section 8.12
- Use Map atomic operations such as merge(), compute(), or putIfAbsent() when concurrent updates must not lose changes. section 9.12
- Recheck computeIfAbsent() behavior after Java upgrades; TreeMap changes in Java 15 can expose previously unnoticed misuse. section 8.12
