How to Use Java ConcurrentHashMap Correctly
In this tutorial, you'll learn about How to Use Java ConcurrentHashMap Correctly. We cover key concepts, practical examples, and best practices.
The Problem
Your multi-threaded application uses ConcurrentHashMap but still gets ConcurrentModificationException, inconsistent sizes, or hangs on computeIfAbsent. ConcurrentHashMap is thread-safe but common usage patterns can still cause issues.
Quick Fix
Fix 1: ConcurrentModificationException During Iteration
WRONG — modifying the map while iterating:
ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();
map.put("key1", "value1");
map.put("key2", "value2");
for (String key : map.keySet()) {
if (key.equals("key1")) {
map.remove(key); // May throw ConcurrentModificationException
}
}
RIGHT — use Iterator.remove() or forEach:
// Safe way 1: forEach with concurrent modification
map.forEach((key, value) -> {
if (key.equals("key1")) {
map.remove(key); // safe in ConcurrentHashMap's forEach
}
});
// Safe way 2: use entrySet iterator
Iterator<Map.Entry<String, String>> it = map.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<String, String> entry = it.next();
if (entry.getKey().equals("key1")) {
it.remove(); // safe
}
}
Fix 2: size() Is Approximate
WRONG — relying on size() for exact counts:
int size = map.size();
if (size > 0) {
map.clear(); // Between size() and clear(), another thread added an entry
}
RIGHT — use mappingCount() for better accuracy and design for races:
long count = map.mappingCount(); // returns long, more accurate for large maps
// But still approximate — use atomic operations for correctness:
if (map.isEmpty()) {
// no guarantee the map stays empty
}
Fix 3: computeIfAbsent and computeIfPresent Deadlock
// Deadlock risk in Java 8:
map.computeIfAbsent("key", k -> {
map.computeIfAbsent("other-key", k2 -> "value"); // May deadlock!
return "value";
});
RIGHT — avoid nested compute calls:
// Pre-compute the value outside:
String otherValue = map.get("other-key");
if (otherValue == null) {
otherValue = "value";
}
map.computeIfAbsent("key", k -> {
// Do not call computeIfAbsent inside
return otherValue;
});
Fix 4: Iteration Is Not Atomic
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
map.put("count", 0);
// WRONG — read-then-write race:
int current = map.get("count");
map.put("count", current + 1); // Another thread may have updated between get and put
RIGHT — use atomic operations:
// Use compute for atomic read-modify-write:
map.compute("count", (key, value) -> (value == null) ? 1 : value + 1);
// Or use merge:
map.merge("count", 1, Integer::sum);
Fix 5: Null Values Not Allowed
map.put("key", null); // Throws NullPointerException!
RIGHT — use an alternative or check for null:
if (someValue != null) {
map.put("key", someValue);
}
Fix 6: Bulk Operations Are Not Atomic
// putAll is not atomic relative to other operations
map.putAll(otherMap);
// Between putAll calls, another thread may see partial state
RIGHT — use a separate lock for compound operations:
synchronized (map) {
map.clear();
map.putAll(newData);
}
Use DodaTech's Concurrency Debugger to visualize thread interactions with ConcurrentHashMap and detect race conditions in your application.
Prevention
- Use
compute,merge, orforEachfor atomic operations. - Avoid nested
computeIfAbsentcalls. - Do not rely on
size()for exact counts. - Use
<a href="/design-patterns/iterator/">Iterator</a>.remove()during iteration. - Design for concurrency — assume the map changes between operations.
Common Mistakes with concurrent hashmap
- Forgetting
deriving (Show, Eq)on custom data types needed for debugging - Placing the wildcard pattern first in case expressions, making all subsequent patterns unreachable
- Using
headandtailinstead of pattern matching, causing runtime errors on empty lists
These mistakes appear frequently in real-world JAVA code. DodaTech's contributors have identified these patterns through analysis of open-source projects and production systems.
Practice Exercise
Write a pure function that safely divides two integers using Maybe, then test it with edge cases like division by zero and negative numbers.
This exercise reinforces the concepts covered in this guide. Try implementing it before checking online solutions.
FAQ
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro