πΊ️ Java Maps Explained: HashMap vs Hashtable vs ConcurrentHashMap
Java provides several map implementations, but not all of them are suitable for multi-threaded environments. Choosing the wrong one can lead to performance bottlenecks, thread-safety issues, or even corrupted data. In this post, we’ll explore the differences between HashMap, Hashtable, and ConcurrentHashMap, explain when to use each, and dive into how ConcurrentHashMap works under the hood.
1. πΊ️ HashMap
HashMap
is the standard, non-thread-safe map in Java. It allows null
keys and values and provides fast lookups with O(1) average time complexity.
Map<String, Integer> map = new HashMap<>();
map.put("apple", 1);
map.put("banana", 2);
Integer value = map.get("apple");
✅ Fast and memory-efficient for single-threaded applications or maps confined to one thread.
❌ Not safe to use in multiple threads without external synchronization.
2. π§± Hashtable
Hashtable
is a legacy map that is thread-safe because all its methods are synchronized. However, this coarse-grained locking means only one thread can access any method at a time, even for reads.
Map<String, Integer> map = new Hashtable<>();
map.put("apple", 1);
map.put("banana", 2);
Integer value = map.get("apple");
✅ Thread-safe out-of-the-box.
❌ Very inefficient under high concurrency because all operations are synchronized.
π‘ Avoid using it in modern applications; prefer ConcurrentHashMap
.
3. ⚡ ConcurrentHashMap
ConcurrentHashMap
is the modern solution for concurrent maps. It is thread-safe and optimized for high performance with multiple threads.
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
map.put("apple", 1);
map.put("banana", 2);
map.merge("apple", 1, Integer::sum); // atomic update
Integer value = map.get("apple");
✅ Allows multiple threads to read and write safely without locking the entire map.
✅ Atomic per-key operations like computeIfAbsent
and merge
.
4. π HashMap vs Hashtable vs ConcurrentHashMap
Feature | HashMap | Hashtable | ConcurrentHashMap |
---|---|---|---|
Thread-safe | ❌ No | ✅ Yes (coarse sync) | ✅ Yes (fine-grained / CAS) |
Allows null keys/values | ✅ Yes | ❌ No | ❌ No |
Performance under concurrency | ❌ Unsafe | ⚠ Slow (all methods synchronized) | ✅ High (reads are lock-free, writes use bucket-level locks) |
Scalability | ❌ Limited to single thread | ⚠ Poor | ✅ Good |
Use case | Single-threaded map | Legacy multi-threaded | Modern concurrent applications |
5. π ️ How ConcurrentHashMap Works
ConcurrentHashMap is designed for **high concurrency**. Its core concepts:
- Lock-free reads:
get()
operations don’t block and see the most recent value. - Fine-grained locking: Writes like
put()
orremove()
lock only the affected bucket, not the entire map. - CAS operations: Internal atomic compare-and-swap ensures safe updates without locking the whole map.
- Treeification: Buckets with high collisions turn into balanced trees for O(log N) lookups.
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
map.put("apple", 1);
map.computeIfPresent("apple", (k, v) -> v + 1); // atomic per key
✅ Efficient for thousands of threads reading and updating simultaneously.
6. π Best Practices
- Use
HashMap
for single-threaded applications only. - Avoid
Hashtable
in new code; it’s legacy and inefficient. - Use
ConcurrentHashMap
when multiple threads need safe access to shared maps. - Prefer
computeIfAbsent
ormerge
for atomic updates instead of manual synchronization. - Do not assume iteration is snapshot-consistent — iterators are weakly consistent, meaning they reflect some state of the map while allowing concurrent updates.
7. π Example: Atomic Counter per Key
One common use of ConcurrentHashMap is maintaining counters safely for multiple threads.
ConcurrentHashMap<String, AtomicInteger> counters = new ConcurrentHashMap<>();
Runnable incrementTask = () -> {
counters.computeIfAbsent("apple", k -> new AtomicInteger()).incrementAndGet();
};
Thread t1 = new Thread(incrementTask);
Thread t2 = new Thread(incrementTask);
t1.start(); t2.start();
t1.join(); t2.join();
System.out.println("Apple count: " + counters.get("apple").get());
✅ Safe increment per key without locking the entire map.
Conclusion
Choosing the right map implementation is crucial for performance and correctness in concurrent Java applications. HashMap is fast but unsafe for multi-threading, Hashtable is thread-safe but outdated, and ConcurrentHashMap provides modern, high-performance concurrency with atomic per-key operations. Understanding its implementation helps you write thread-safe, scalable applications efficiently.
Labels: Java, Concurrency, HashMap, Hashtable, ConcurrentHashMap, Multithreading, Performance, Thread Safety
Comments
Post a Comment