In multi-threaded Java applications, ConcurrentHashMap
is a go-to collection for safely handling concurrent access to key-value pairs without external synchronization. It provides better scalability than Collections.synchronizedMap()
or Hashtable
, and its internal design has evolved significantly—from segmentation in Java 7 to bucket-level locking and CAS (Compare-And-Swap) in Java 8+.
This article explores everything from syntax and internals to real-world use cases, performance comparisons, and Java version-specific changes.
Definition and Purpose
ConcurrentHashMap<K, V>
is a thread-safe implementation of Map
that allows safe, concurrent access and updates from multiple threads. It avoids locking the entire map and instead uses fine-grained locking or lock-free mechanisms depending on the operation.
Java Syntax and Class Structure
import java.util.concurrent.ConcurrentHashMap;
ConcurrentHashMap<String, Integer> inventory = new ConcurrentHashMap<>();
inventory.put("apple", 10);
inventory.putIfAbsent("banana", 5);
inventory.compute("apple", (k, v) -> v + 1);
Internal Working – Java 7 vs Java 8+
Java 7 – Segmented Locking
- Used Segment[] segments, where each segment was essentially a smaller hash table with its own lock.
- Reads could proceed without locking; writes locked only one segment.
- Limited concurrency (default 16 segments).
Java 8+ – Bucket-Level Locking
- Removed
Segment
entirely. - Uses a flat
Node[] table
with synchronized locking on bins and CAS operations for atomic updates. - Enables finer granularity and better concurrency.
🔍 Analogy: Java 7 locked entire drawers (segments); Java 8+ locks only individual folders (buckets).
Bucket Locking and Tree Binning
- Bucket locking is achieved using
synchronized
blocks on individual bins. - When hash collisions exceed a threshold (TREEIFY_THRESHOLD = 8), the bin becomes a red-black tree for faster lookup.
map.merge("user", 1, Integer::sum); // Lock-free read and atomic write
Performance and Big-O Complexity
Operation | Average Time | Worst Case | Thread-Safe | Notes |
---|---|---|---|---|
get | O(1) | O(n) | ✅ | Lock-free |
put | O(1) | O(n) | ✅ | Uses CAS/synchronized on bin |
remove | O(1) | O(n) | ✅ | |
iteration | O(n) | O(n) | ✅ | Weakly consistent |
Real-World Use Cases
- Caching layers (e.g., LRU cache)
- Shared counters or metrics (e.g., API hit counter)
- Session or state management in concurrent web applications
Functional Features from Java 8+
map.computeIfAbsent("config", key -> loadConfig(key));
map.merge("pageHits", 1, Integer::sum);
map.forEach((k, v) -> System.out.println(k + ": " + v));
Java 8 methods simplify atomic and conditional updates.
Pros and Cons
✅ Pros
- Safe concurrent access without external locks
- Fine-grained bucket-level concurrency
- Fast reads with CAS support
❌ Cons
- Weakly consistent iterators (not real-time)
- No null keys or values allowed
- Not ideal for access-order use cases
Anti-Patterns and Misuse
- ❌ Using
ConcurrentHashMap
with external synchronization - ❌ Relying on
size()
for exact count in concurrent context - ❌ Using compute/merge recursively
Refactoring Legacy Code
Before:
Map<String, Integer> safeMap = Collections.synchronizedMap(new HashMap<>());
After:
ConcurrentMap<String, Integer> safeMap = new ConcurrentHashMap<>();
Best Practices
- Use
computeIfAbsent
instead of manual null checks. - Avoid nested locking or synchronized blocks outside the map.
- Don’t rely on iteration order or exact size during concurrent updates.
📌 What's New in Java Versions
Java 8
- Replaced segment structure with flat bin array
- Introduced
merge
,compute
, and functional APIs - Improved bucket concurrency with tree binning
Java 9
- Improved memory layout and GC behavior
Java 10–17
- Local variable inference (
var
) - GC optimizations
Java 21
- Structured concurrency simplifies map orchestration in virtual-threaded apps
- Better parallel throughput due to virtual thread scalability
Conclusion and Key Takeaways
ConcurrentHashMap
has evolved from segmented locking (Java 7) to bucket-level locking (Java 8+).- It's the default choice for most concurrent map use cases.
- Functional APIs like
computeIfAbsent
andmerge
greatly simplify thread-safe programming.
FAQ
1. Is ConcurrentHashMap faster than HashMap with synchronized block?
Yes, due to finer-grained locking and CAS support.
2. What replaced segments in Java 8?
A flat table array (Node[]
) with synchronized bin locking.
3. Does it support null keys or values?
No. Use optional wrappers or special tokens instead.
4. Are iterators fail-fast?
No. Iterators are weakly consistent and do not throw ConcurrentModificationException
.
5. When does bin turn into a tree?
When collisions in a bucket exceed 8 entries.
6. What’s the default concurrency level?
In Java 8+, there's no fixed concurrency level—locking is bucket-based.
7. Can I use compute and put in the same map safely?
Yes, but avoid chaining compute/merge operations.
8. What’s better for frequent writes: HashMap or ConcurrentHashMap?
Always use ConcurrentHashMap
in concurrent contexts.
9. Can I iterate safely during concurrent modification?
Yes. But the view may be stale or incomplete.
10. Should I use ConcurrentHashMap for everything?
No. For single-threaded or immutable data, use simpler maps for better performance.