# 问题

7. ConcurrentHashMap 采用了哪些锁优化机制?为什么比 Hashtable 高效?

# 标准答案

ConcurrentHashMap 采用 CAS(无锁操作)+ Synchronized(局部锁)+ Volatile(可见性)+ 红黑树(减少冲突) 的综合锁优化机制,实现更高效的并发控制。相较于 Hashtable 使用的全局 Synchronized 锁,ConcurrentHashMap 通过更细粒度的并发控制 降低锁竞争,大幅提升读写性能。

# 答案解析

# 1. 为什么 Hashtable 效率低?

Hashtable 的所有方法(put() / get() / remove() 等)都使用了 synchronized 关键字,导致:

  1. 全局锁竞争严重:所有线程竞争同一把锁,读写都串行化,读写互斥,影响吞吐量。
  2. 锁粒度大,阻塞严重:即使不同 key 存在不同的哈希桶,访问仍然需要获取全局锁,不能并发操作多个桶。
  3. 扩容时卡顿严重:整个 Hashtable 扩容时需要重新计算所有 key 的 hashCode(),并且是单线程执行,影响性能。

示例:多线程 put() 操作会被全局锁同步,导致高并发下性能下降:

public synchronized V put(K key, V value) {
    int hash = key.hashCode();
    ...
}
1
2
3
4

# 2. ConcurrentHashMap 的锁优化机制

# (1)CAS(无锁操作)降低竞争

put() 操作中,ConcurrentHashMap 先尝试使用 CAS(compareAndSwap)无锁操作 更新数据,避免加锁。

  • 若目标桶(bin)为空,则 CAS 直接插入新节点。
  • 若有哈希冲突,则 synchronized 仅锁定该 bin,减少锁竞争。
if (casTabAt(tab, i, null, new Node<K,V>(hash, key, val, null))) { 
    // 通过 CAS 操作无锁插入
}
1
2
3

优势:多数情况下,无需加锁即可更新数据,提高并发性能。

# (2)Synchronized 局部锁(代替全局锁)

如果 CAS 失败(即发生哈希冲突),ConcurrentHashMap 只对该哈希桶(bin)加锁,而非整个 Map,加速并发写入。

synchronized (f) { 
    // 只在哈希冲突时锁定该 bin
}
1
2
3

优势:只影响冲突的 Key,不影响其他 Key 的并发写入,锁粒度更小,并发度更高。

# (3)Volatile 保证可见性,避免锁开销
  • Node.valtable 采用 volatile 修饰,确保修改后对其他线程可见,避免 synchronized 额外的锁开销。
static class Node<K,V> {
    final int hash;
    final K key;
    volatile V val;
    volatile Node<K,V> next;
}
1
2
3
4
5
6

优势:读操作可以无锁完成,提高吞吐量。

# (4)红黑树减少高并发下的哈希冲突
  • 当链表长度超过 8 时,链表转换为红黑树,查询时间复杂度 O(n) → O(log n)。
  • 避免哈希冲突退化为链表遍历,提高写入和查找效率。
if (binCount >= TREEIFY_THRESHOLD)
    treeifyBin(tab, i);
1
2

优势:降低哈希冲突,提升查询和插入性能。

# (5)并行扩容,避免全局锁竞争

JDK 1.8 采用分段扩容,允许多个线程并发迁移不同的桶,提升扩容效率。

  • 旧版 Hashtable 需要全局锁扩容,所有操作暂停,影响吞吐量。
  • ConcurrentHashMap 通过 ForwardingNode 让线程知道桶已迁移,避免竞争。
Node<K,V> f;
if ((f = tabAt(nextTab, i)) == null)
    casTabAt(nextTab, i, new Node<K,V>(fh, null, null, null));
1
2
3

优势:多个线程同时扩容,提升效率,避免全局阻塞。

# 3. 并发性能对比(ConcurrentHashMap vs Hashtable)

特性 Hashtable(JDK 1.7) ConcurrentHashMap(JDK 1.8)
加锁机制 全局 synchronized CAS + Synchronized 局部锁
锁粒度 整个 Map 仅锁定哈希桶
读性能 受锁影响,低 读操作基本无锁,高
写性能 线程争抢全局锁,低 CAS + 局部锁,高
扩容 全局锁,扩容卡顿 并发扩容,吞吐量高
高并发表现 线程阻塞严重 低冲突,高吞吐量

# 4. ConcurrentHashMap 的常见问题

常见误区

  1. 误以为 ConcurrentHashMap 读写都无锁

    • 实际上,读操作基本无锁,但写操作(哈希冲突)时仍然会对某个 binsynchronized
  2. 误认为 JDK 1.8 仍然使用 Segment

    • JDK 1.8 完全去除了 Segment,直接使用 Node[] 结构,配合 CAS + synchronized 实现高并发。
  3. 忽视扩容带来的性能波动

    • 并发扩容虽然减少了全局锁竞争,但迁移数据仍然有成本,可能影响短时间内的性能。

# 深入追问

🔹 为什么 ConcurrentHashMap 选择 synchronized 而不是 ReentrantLock
🔹 为什么红黑树转换阈值是 8,而不是更小?
🔹 putIfAbsent() 方法在高并发环境下是否一定安全?
🔹 并行扩容时如何避免数据丢失?

# 相关面试题

ConcurrentHashMap 的并发优化机制?
为什么 ConcurrentHashMap 读操作比 Hashtable 快?
ConcurrentHashMap 在高并发场景下的性能优化点?