一句话说透Java里面的ConcurentHashMap加锁机制是什么?

225 阅读3分钟

Java中的ConcurrentHashMap加锁机制详解

ConcurrentHashMap 是Java中高效的线程安全哈希表,其加锁机制经过多次优化,核心设计为 减小锁粒度 和 无锁化操作,以提升并发性能。以下是其加锁机制的详细解析:


一、Java 7及之前的分段锁(Segment)

  1. 分段设计

    • 将整个哈希表分为多个 段(Segment) ,每个段相当于独立的哈希表。
    • 默认16个段,允许最多16个线程并发操作不同段,减少竞争。
  2. 锁机制

    • 每个段继承自 ReentrantLock,操作前需获取对应段的锁。
    • 优点:段间操作互不干扰,提高并发度。
    • 缺点:段数固定,扩容不够灵活。

二、Java 8及之后的优化:CAS + synchronized

Java 8摒弃分段锁,采用更细粒度的 桶级别锁 和 CAS无锁操作,结合以下机制:

1. 无锁化操作(CAS)

  • 适用场景:初始化、插入空桶、更新值等。

    • 示例:插入新节点时,若桶为空,直接通过CAS设置头节点,无需加锁。

    if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value)))
        break;
    

2. 细粒度锁(synchronized)

  • 锁对象:每个桶的头节点(链表或红黑树根节点)。

    • 操作非空桶时,使用 synchronized 锁住头节点。
    • 优点:仅锁当前桶,其他桶操作不受影响。
    synchronized (f) {
        // 处理链表或红黑树
    }
    

3. 扩容协作

  • 多线程协同扩容

    • 当线程插入数据时发现正在扩容,会协助迁移数据。
    • 通过 ForwardingNode 标记迁移状态,避免重复操作。
    • 优点:加快扩容速度,减少性能波动。

三、核心操作流程

1. 插入元素(put)

  1. 计算哈希定位到桶。
  2. 桶为空:尝试CAS插入新节点。
  3. 桶非空:锁住头节点,处理链表或红黑树。
  4. 检查是否需要扩容,触发迁移。

2. 获取元素(get)

  • 全程无锁:依赖 volatile 保证可见性,直接遍历链表或树。

3. 扩容(transfer)

  1. 分配新数组,容量翻倍。
  2. 多线程协作迁移旧数据,每个线程负责一段区间。
  3. 迁移完成,替换旧数组引用。

四、关键设计优势

  1. 锁粒度极细:仅锁单个桶,不同桶操作完全并行。
  2. CAS减少锁竞争:空桶插入等场景无需加锁。
  3. 并发扩容:多线程协同迁移数据,降低扩容延迟。

五、性能对比

实现方式锁粒度并发度适用场景
Hashtable全表锁低(单线程操作)遗留代码兼容
分段锁(Java 7)段锁(默认16段)中(段间并发)中等并发
CAS + synchronized(Java 8+)桶锁高(桶间完全并发)高并发、大数据量

六、总结

  • Java 7:通过 分段锁 提升并发,但段数固定,灵活性不足。
  • Java 8+ :采用 桶锁 + CAS,锁粒度更细,支持动态扩容协作,性能显著提升。
  • 适用场景:高并发读写(如缓存、计数器)、需线程安全且高效的数据存储。

核心口诀
「ConcurrentHashMap 强,线程安全效率高
Java 7 分段锁,段间并发性能保
Java 8 更优化,CAS 加锁粒度小
桶锁配合协同扩,高并发下稳如锚!」