简识ConcurrentHashMap 高并发下线程安全原理

ConcurrentHashMap 是 Java 中线程安全的哈希表实现,专为高并发场景设计。它在保留 HashMap 高效查询特性的同时,通过分段锁、CAS 操作和细粒度同步等机制实现线程安全。以下是其核心设计原理和关键特性:


1. 线程安全实现(Java 8 及之后)​

a. 分段锁 → 节点级锁

  • Java 7:采用 ​分段锁(Segment)​,将整个哈希表划分为多个段(Segment),每个段独立加锁,降低锁竞争。
  • Java 8 及之后:优化为 ​节点级锁​(锁住单个桶的头节点),进一步减少锁粒度,提升并发度。

b. 关键机制

  • CAS(Compare-And-Swap)​:用于初始化、空桶插入等无竞争场景,避免加锁。
  • synchronized:对非空桶的头节点加锁,保证写操作线程安全。
  • volatile:桶数组和节点中的值用 volatile 修饰,保证可见性。

2. 核心操作流程(以 put 为例)​

  1. 计算哈希值:与 HashMap 类似,使用扰动函数生成哈希值。
  2. 定位桶位置(n - 1) & hash 确定键值对所在的桶。
  3. 插入或更新
    • 空桶:CAS 尝试直接插入新节点,无需锁。
    • 非空桶:对头节点加 synchronized 锁,遍历链表或红黑树插入/更新。
  4. 扩容支持:多线程协同扩容,当前线程插入时若发现正在扩容,会协助迁移数据。

3. 关键特性

a. 并发度与性能

  • 高并发读:读操作完全无锁(依赖 volatile 可见性)。
  • 高并发写:不同桶的写操作互不影响,仅相同桶的写操作会竞争同一把锁。

b. 扩容机制

  • 多线程协助扩容:当某个线程触发扩容(元素数 ≥ 容量 × 负载因子),其他线程在插入时也会协助迁移数据。
  • 增量迁移:旧数组分块迁移,避免长时间阻塞。

c. 统计大小(size())​

  • 分段计数:通过 CounterCell[] 分散统计,减少竞争。
  • 最终一致性:返回的 size 是近似值(并发环境下精确计数成本高)。

4. 与 HashMap、Hashtable 的对比

ConcurrentHashMapHashMapHashtable
线程安全是(分段锁/CAS+节点锁)是(全局锁,性能低)
锁粒度节点级锁(Java 8)无锁全局锁(整个表加锁)
并发度
Null 键/值不允许允许一个 null 键/值不允许
迭代器弱一致性(安全但可能过时)快速失败(Fail-Fast)快速失败(Fail-Fast)

5. 使用场景

  • 高并发读写:如缓存、实时计数器等。
  • 替代 Hashtable:需要线程安全且更高性能的场景。
  • 避免锁竞争:适合写操作分散在不同桶的场景。

6. 注意事项

  • 非原子组合操作:例如 computeIfAbsent 是原子的,但多个独立操作(如先检查后插入)仍需外部同步。
  • 批量操作forEachsearch 等方法支持并行处理,但结果依赖并发状态。
  • 初始容量与并发级别:根据预估并发数设置合理的初始容量,避免频繁扩容。

源码简析(Java 8)​

final V putVal(K key, V value, boolean onlyIfAbsent) {
    if (key == null || value == null) throw new NullPointerException();
    int hash = spread(key.hashCode()); // 计算哈希值
    int binCount = 0;
    for (Node<K,V>[] tab = table;;) {
        Node<K,V> f; int n, i, fh;
        if (tab == null || (n = tab.length) == 0)
            tab = initTable(); // 初始化表(CAS)
        else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
            if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value)))
                break; // CAS 插入空桶成功
        } else if ((fh = f.hash) == MOVED)
            tab = helpTransfer(tab, f); // 协助扩容
        else {
            synchronized (f) { // 锁住头节点
                // 遍历链表或红黑树插入/更新
                if (tabAt(tab, i) == f) {
                    if (fh >= 0) {
                        // 处理链表
                    } else if (f instanceof TreeBin) {
                        // 处理红黑树
                    }
                }
            }
        }
    }
    addCount(1L, binCount); // 更新计数,可能触发扩容
    return null;
}

总结

ConcurrentHashMap 通过 ​CAS + synchronized + volatile​ 的组合策略,在保证线程安全的同时,最大化并发性能。理解其设计哲学和关键机制,有助于在高并发场景中合理使用并优化性能。

(望各位潘安、各位子健/各位彦祖、于晏不吝赐教!多多指正!🙏)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值