Cả 2 thread-safe, nhưng cách hoàn toàn khác.
| Hashtable (legacy) | ConcurrentHashMap (Java 5+) | |
|---|---|---|
| Lock | synchronized toàn method — 1 thread tại 1 thời điểm | Bucket-level + CAS (lock-free read) |
| Throughput multi-thread | Thấp (contention cao) | Cao hơn nhiều lần |
| Iterator | Fail-fast → CME | Weakly-consistent — không throw |
| Compound op atomic | ❌ | ✅ putIfAbsent, compute, merge |
Java 8+: ConcurrentHashMap bỏ segment, dùng CAS + synchronized per-node, bucket >8 entry biến thành red-black tree.
java
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
// Compound op ATOMIC
map.compute("counter", (k, v) -> v == null ? 1 : v + 1);
map.merge("hits", 1, Integer::sum);Tránh anti-pattern:
java
// ❌ SAI — race condition giữa get và put
if (!map.containsKey(key)) map.put(key, computeValue());
// ✅ ĐÚNG — atomic
map.computeIfAbsent(key, k -> computeValue());Default 2026: ConcurrentHashMap cho mọi shared map. Hashtable chỉ còn ý nghĩa lịch sử.
Both are thread-safe, but in completely different ways.
| Hashtable (legacy) | ConcurrentHashMap (Java 5+) | |
|---|---|---|
| Locking | synchronized on every method — one thread at a time | Bucket-level + CAS (lock-free reads) |
| Multi-thread throughput | Low (high contention) | Much higher |
| Iterator | Fail-fast → CME | Weakly-consistent — no throw |
| Atomic compound ops | ❌ | ✅ putIfAbsent, compute, merge |
Java 8+: ConcurrentHashMap dropped segments, uses CAS + per-node synchronized, buckets with >8 entries become red-black trees.
java
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
// Compound ops are ATOMIC
map.compute("counter", (k, v) -> v == null ? 1 : v + 1);
map.merge("hits", 1, Integer::sum);Avoid the anti-pattern:
java
// ❌ WRONG — race between get and put
if (!map.containsKey(key)) map.put(key, computeValue());
// ✅ RIGHT — atomic
map.computeIfAbsent(key, k -> computeValue());2026 default: ConcurrentHashMap for any shared map. Hashtable is only of historical interest.