πŸ—Ί️ 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

FeatureHashMapHashtableConcurrentHashMap
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 caseSingle-threaded mapLegacy multi-threadedModern 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() or remove() 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 or merge 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

Popular posts from this blog

πŸ› ️ The Code Hut - Index

πŸ“˜ Distributed Systems with Java — Series Index

πŸ”„ Distributed Transactions Deep Dive