ConcurrentHashMap CAS 详解

ConcurrentHashMap 的 CAS 详解

ConcurrentHashMap 是 Java 提供的线程安全的 HashMap 实现,它广泛应用于高并发场景。其核心性能的实现依赖于 CAS (Compare-And-Swap) 操作来确保线程安全,同时避免锁的竞争,从而提高并发性能。


1. 什么是 CAS (Compare-And-Swap)

  • 核心思想
    CAS 是一种无锁的原子操作,通过硬件指令(如 x86 的 CMPXCHG)直接支持。

  • 基本流程

    1. 比较某个内存位置的当前值是否与预期值相等。
    2. 如果相等,则更新为新的值。
    3. 如果不相等,则说明有其他线程修改过该值,CAS 操作失败。
  • 优点

    • 无需加锁,减少线程竞争,提升性能。
  • 缺点

    • 存在 ABA 问题(可以通过加版本号解决)。
    • 自旋失败会造成 CPU 消耗。

2. ConcurrentHashMap 的基本结构

ConcurrentHashMap 是线程安全的哈希表,其内部采用分段(Segment)锁和链表/红黑树结合的方式存储数据(JDK 1.8 之前是基于分段锁实现,1.8 之后结构优化)。

在 JDK 1.8 中:

  1. ConcurrentHashMap 采用数组(Node<K,V>[])+ 链表 + 红黑树的结构。
  2. 对于高并发操作,利用 CAS 和 同步块 (synchronized) 共同保障线程安全。

3. CAS 在 ConcurrentHashMap 中的应用

ConcurrentHashMap 的许多操作依赖 CAS 保证线程安全。以下是 CAS 在不同场景中的应用详解:

3.1 初始化表

  • 当第一次插入元素时,需要初始化哈希表(table)。
  • 使用 CAS 确保多个线程不会重复初始化:
    if (table == null || table.length == 0) {
        Node<K,V>[] newTable = new Node[newCapacity];
        if (U.compareAndSwapObject(this, TABLE, null, newTable)) {
            // 初始化成功
        }
    }
    
    流程
    • 检查 table 是否为 null
    • 如果为 null,尝试通过 CAS 将 table 设置为新的数组。
    • 如果其他线程已经初始化成功,则当前线程的 CAS 操作失败,直接使用已有的 table

3.2 put 操作

当插入新元素时,ConcurrentHashMap 通过 CAS 保证节点插入的原子性:

(1) 插入新节点
  • 如果目标桶(table[index])为空,使用 CAS 将新节点插入:
    if (U.compareAndSwapObject(tab, i, null, new Node<K,V>(hash, key, value, null))) {
        // 插入成功
    }
    
    流程
    • 判断当前桶是否为空。
    • 如果为空,利用 CAS 将节点插入。如果失败,说明有其他线程同时插入,进入下一步逻辑。

3.3 扩容(rehash)

当哈希表需要扩容时,CAS 被用于协调多个线程完成迁移操作:

(1) 设置扩容标志
  • 多个线程可能同时检测到需要扩容,只允许一个线程完成初始化新表:
    if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
        // 当前线程负责扩容
    }
    
(2) 转移节点
  • 在扩容过程中,多个线程会协作迁移数据,每次迁移时使用 CAS 确保线程安全。

3.4 get 操作

ConcurrentHashMapget 操作是线程安全的,因为读取操作无需加锁,仅需要保证可见性,使用 volatile 或 CAS。

  • 数据读取是无锁的,因为 Node 的链表结构和红黑树的构建本质上是线程安全的。

4. CAS 操作的优缺点在 ConcurrentHashMap 中的体现

优点

  1. 高并发性能
    • 通过 CAS 避免了全局锁(如 synchronizedReentrantLock),减少了线程阻塞。
  2. 局部性优化
    • 操作只针对某个桶(table[index]),线程之间竞争更小。

缺点

  1. ABA 问题
    • 如果节点的引用发生了变化(例如被移除后又重新插入),可能导致 CAS 操作误判。JDK 的 AtomicStampedReference 提供了解决方案。
  2. 自旋问题
    • 当 CAS 操作失败时,可能需要反复重试,自旋会消耗 CPU 资源。

5. 总结

核心点

  • CAS 的核心作用

    1. 保证 ConcurrentHashMap 的线程安全。
    2. 避免了锁的频繁使用,提升了性能。
  • CAS 使用场景

    • 初始化表时,保证单次初始化。
    • 插入节点时,确保原子性。
    • 扩容时,协作完成迁移。

适用场景

ConcurrentHashMap 适用于高并发读写的场景,例如:

  1. 缓存存储。
  2. 计数器或频率统计。
  3. 并发访问的集合管理。

通过 CAS 与同步块的结合,ConcurrentHashMap 在保证线程安全的同时提供了优秀的性能表现。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

飞滕人生TYF

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值