Keywords: ConcurrentHashMap | Synchronized HashMap | Java Concurrency
Abstract: This paper provides an in-depth comparison between ConcurrentHashMap and synchronized HashMap wrappers in Java concurrency scenarios. It examines the fundamental locking mechanisms: synchronized HashMap uses object-level locking causing serialized access, while ConcurrentHashMap employs fine-grained locking through segmentation. The article details how ConcurrentHashMap supports concurrent read-write operations, avoids ConcurrentModificationException, and demonstrates performance implications through code examples. Practical recommendations for selecting appropriate implementations in high-concurrency environments are provided.
Fundamental Differences in Locking Mechanisms
In Java concurrent programming, thread-safe Map implementations primarily come in two forms: HashMap wrapped with Collections.synchronizedMap (referred to as Synchronized HashMap) and ConcurrentHashMap. The core distinction lies in their locking granularity.
Synchronized HashMap employs object-level locking. When invoking Collections.synchronizedMap(new HashMap<>()), all methods of the returned Map instance are wrapped in synchronized blocks using a single lock object. This means any thread executing operations like put(), get(), or remove() acquires the lock for the entire Map. For example:
// Typical usage of Synchronized HashMap
Map<String, Integer> syncMap = Collections.synchronizedMap(new HashMap<>());
// All operations use the same lock
synchronized(syncMap) {
syncMap.put("key", 1);
Integer value = syncMap.get("key");
}
This coarse-grained locking creates significant performance bottlenecks. When one thread holds the lock, all other threads are blocked from accessing the Map, even for read-only operations. In code like print("<T>"), angle brackets must be escaped to prevent HTML tag parsing.
Segmented Locking Design of ConcurrentHashMap
ConcurrentHashMap (introduced in JDK 5) adopts a fundamentally different concurrency strategy. It implements fine-grained concurrency control through segmented locking (Segment) or bucket-level locking (CAS+synchronized since JDK 8).
In JDK 7 implementation, ConcurrentHashMap internally divides data into multiple segments, each with its own lock. This allows different threads to access different segments concurrently, significantly improving concurrency. For instance, while one thread modifies segment A, another can simultaneously read data from segment B. Code example:
// Basic operations with ConcurrentHashMap
ConcurrentHashMap<String, Integer> concurrentMap = new ConcurrentHashMap<>();
// Concurrent read-write without explicit synchronization
concurrentMap.put("key1", 100);
Integer result = concurrentMap.get("key1");
// Safe iteration
for (Map.Entry<String, Integer> entry : concurrentMap.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
This design enables read operations to typically proceed without locking (ensured by volatile variables), while write operations only lock affected portions rather than the entire collection. The article also discusses the essential difference between <br> and the \n character, where <br> as a described object requires escaping.
Concurrent Modification and Iteration Safety
Another critical difference involves behavior during concurrent modification while iterating. Synchronized HashMap throws ConcurrentModificationException when modified by another thread during iteration. This occurs because iterators maintain a modification counter and fail-fast upon detecting unexpected changes.
Conversely, ConcurrentHashMap iterators are weakly consistent. They do not throw ConcurrentModificationException, permitting modifications during iteration. Iterators are based on snapshots or current states at creation time and may or may not reflect subsequent changes. For example:
// Safe iteration example with ConcurrentHashMap
ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();
map.put("a", "1");
map.put("b", "2");
// Thread 1: Iteration
new Thread(() -> {
for (String key : map.keySet()) {
System.out.println(key);
try { Thread.sleep(100); } catch (InterruptedException e) {}
}
}).start();
// Thread 2: Concurrent modification
new Thread(() -> {
map.put("c", "3");
map.remove("a");
}).start();
This design avoids global locking during iteration, enhancing concurrent performance, but programmers must be aware that iteration results might not be up-to-date.
Performance Implications and Application Scenarios
According to Brian Goetz's analysis in the IBM developer article, ConcurrentHashMap delivers significantly higher throughput in high-concurrency scenarios. Tests show that with 16 concurrent threads, ConcurrentHashMap can achieve an order of magnitude higher throughput compared to Synchronized HashMap.
Selection recommendations:
- For low-concurrency scenarios or those with minimal write operations, Synchronized HashMap may suffice
- For high-concurrency read-write scenarios, particularly read-heavy workloads,
ConcurrentHashMapis preferable - When safe concurrent iteration is required,
ConcurrentHashMapis mandatory
Since JDK 8, ConcurrentHashMap has been further optimized, replacing segmented locking with CAS operations and synchronized, reducing memory overhead and improving performance in small-scale concurrency.