Answer
ConcurrentHashMap vs HashMap vs Hashtable
Quick Comparison
| Feature | HashMap | Hashtable | ConcurrentHashMap |
|---|---|---|---|
| Thread-safe | No | Yes (full lock) | Yes (partial lock) |
| Performance | Fast | Slow (lock on all ops) | High concurrency |
| Null keys | 1 allowed | Not allowed | Not allowed |
| Null values | Allowed | Not allowed | Not allowed |
| Synchronized | No | Every method | Bucket/node level |
| Java version | 1.2 | 1.0 (legacy) | 1.5 (java.util.concurrent) |
HashMap — Not Thread-Safe
Java
Map<String, Integer> map = new HashMap<>();
// Two threads writing simultaneously → data corruption, infinite loop (Java 7)!
// Thread 1
map.put("Alice", 1);
// Thread 2 (at same time)
map.put("Bob", 2);
// Result: ConcurrentModificationException or data loss!
// Use only in single-threaded context
Map<String, Integer> local = new HashMap<>(); // safe in single thread
Hashtable — Fully Synchronized (Legacy, Avoid)
Java
Map<String, Integer> table = new Hashtable<>();
// Every method is synchronized — only one thread at a time
// Even reads block other reads!
table.put("Alice", 1); // locks entire map
table.get("Alice"); // also locks entire map
// Problem: bottleneck in high-concurrency scenarios
ConcurrentHashMap — Fine-Grained Locking (Best Choice)
Java
Map<String, Integer> concurrentMap = new ConcurrentHashMap<>();
// Java 7: divided into 16 segments, each with own lock
// Java 8: CAS (Compare-And-Swap) operations at node level
// Safe for concurrent reads — no locking at all!
// Safe for concurrent writes — only locks the specific bucket
concurrentMap.put("Alice", 1); // locks only Alice's bucket
concurrentMap.put("Bob", 2); // locks only Bob's bucket — PARALLEL!
Atomic Operations in ConcurrentHashMap
Java
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
map.put("counter", 0);
// putIfAbsent — atomic check-and-insert
map.putIfAbsent("newKey", 42); // only puts if key doesn''t exist (atomic)
// computeIfAbsent — compute value if missing (atomic)
map.computeIfAbsent("user123", key -> loadFromDB(key));
// compute — atomic update
map.compute("counter", (k, v) -> v == null ? 1 : v + 1); // atomic increment!
// merge — atomic merge with function
map.merge("counter", 1, Integer::sum); // adds 1 to existing value
Producer-Consumer with ConcurrentHashMap
Java
ConcurrentHashMap<String, String> sessionCache = new ConcurrentHashMap<>();
// Thread 1 (writing sessions)
Thread writer = new Thread(() -> {
for (int i = 0; i < 100; i++) {
sessionCache.put("session-" + i, "user-" + i);
}
});
// Thread 2 (reading sessions)
Thread reader = new Thread(() -> {
sessionCache.forEach((sessionId, userId) ->
System.out.println("Active: " + sessionId + " → " + userId));
});
writer.start();
reader.start();
// Safe! No ConcurrentModificationException
When to Use What
Java
// 1. Single thread — use HashMap (fastest)
Map<String, String> config = new HashMap<>();
// 2. Legacy code / simple sync — use Collections.synchronizedMap
Map<String, String> syncMap = Collections.synchronizedMap(new HashMap<>());
// Still locks the whole map though
// 3. High concurrency reads + some writes — use ConcurrentHashMap
Map<String, UserSession> sessions = new ConcurrentHashMap<>();
// 4. NEVER use Hashtable in new code — it''s legacy
In Automation Testing (Parallel Tests)
Java
// Share test results across parallel test threads
public class TestResultStore {
private static final ConcurrentHashMap<String, String> results =
new ConcurrentHashMap<>();
public static void record(String testName, String status) {
results.put(testName, status); // thread-safe across parallel tests
}
public static Map<String, String> getAll() {
return Collections.unmodifiableMap(results);
}
public static long countPassed() {
return results.values().stream().filter("PASSED"::equals).count();
}
}
// In any test thread
TestResultStore.record(testName, "PASSED");
