ConcurrentHashMap in Java – Segmentation to Bucket Locking Explained

Illustration for ConcurrentHashMap in Java – Segmentation to Bucket Locking Explained
By Last updated:

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 and merge 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.