Hashtable采用全表锁,所有操作串行化;ConcurrentHashMap(JDK8)按桶加锁,读操作无锁、写操作仅锁冲突桶头节点,并通过CAS优化无竞争场景,显著提升并发性能。

全表锁意味着所有操作串行化
Hashtable 的每个 public 方法(如 put、get、remove)都用 synchronized 修饰,等价于对整个 Map 实例加同一把锁。哪怕只是读一个 key,也要抢这把锁;两个线程分别操作 "user1" 和 "order99",也必须排队执行。这种设计导致:
– 任意时刻最多一个线程在执行任何操作
– 读写互相阻塞,无法并发读
– 高并发下大量线程堆积在锁池,CPU 利用率低但响应延迟高
JDK 8 ConcurrentHashMap 的锁是“按需锁定单个桶”
它不再有 Segment 或全局锁,底层是 Node[] table 数组,每个桶(数组元素)独立管理:
– 读操作完全无锁:靠 volatile Node[] table 和 volatile V val 保证可见性,多个线程可同时读不同 key
– 写操作只锁冲突桶的头节点:比如 key 哈希到索引 5,就只对 table[5] 的链表头或红黑树根加 synchronized
– CAS 用于无竞争场景:插入空桶、更新 sizeCtl、初始化新桶等,直接原子完成,不进锁
性能差异体现在三个典型场景
纯读密集型(如配置缓存)
– Hashtable:QPS 随线程数增加几乎不变,甚至下降(锁争用加剧)
– ConcurrentHashMap:QPS 线性增长,16 线程可接近单线程的 16 倍
读多写少(如会话管理)
– Hashtable:每次 put 都让所有 get 等待,平均延迟飙升
– ConcurrentHashMap:get 不受影响,put 只影响同桶 key,整体吞吐稳定
写冲突集中(如计数器 key 固定为 "total")
– 两者都会在该桶/该表上竞争,但 ConcurrentHashMap 仍占优:
• CAS 失败后才加锁,减少锁进入频率
• synchronized 锁的是轻量级 Node 对象,不是整个 Map 实例
• 支持扩容时多线程协助迁移,避免长时间阻塞
一个直观对比:线程等待行为
假设 10 个线程同时执行 map.get("x"):
– Hashtable:9 个线程立即进入 BLOCKED 状态,等待第 1 个释放锁
– ConcurrentHashMap:10 个线程全部并行执行,无等待
再假设 10 个线程同时执行 map.put("k", v),且哈希后落在 3 个不同桶(比如桶 2、7、13):
– Hashtable:全部串行,总耗时 ≈ 10 × 单次 put 时间
– ConcurrentHashMap:最多 3 组并发执行(每组内若桶相同则仍需串行),实际耗时接近 4~5 × 单次 put 时间



















