ConcurrentHashMap 底层具体实现知道吗?实现原理是什么?

ConcurrentHashMap 是 Java 并发编程中常用的线程安全哈希表实现,其设计目标是在高并发场景下兼顾性能和线程安全。它的底层实现(以 JDK 8 及之后版本为例)结合了多种并发优化技术,以下是其核心实现原理:


1. 数据结构:数组 + 链表 + 红黑树

  • 基础结构:与 HashMap 类似,底层是一个 Node[] 数组,每个数组元素称为一个“桶”(Bucket)。
  • 链表转红黑树:当链表长度超过阈值(默认 8)时,链表会转换为红黑树(时间复杂度从 O(n) 优化为 O(logn));当红黑树节点数小于阈值(默认 6)时,退化为链表。
  • 设计目的:通过红黑树优化哈希冲突严重时的查询效率。

2. 线程安全的核心机制

ConcurrentHashMap 通过以下技术实现高并发下的线程安全:

a. CAS(Compare-And-Swap)
  • 无锁化初始化/扩容:初始化数组或扩容时,通过 CAS 操作保证原子性。
  • 插入空桶:若桶为空(null),直接通过 CAS 写入新节点,避免加锁。
b. synchronized 锁细化
  • 锁单个桶的头节点:当桶非空时,对链表的头节点或红黑树的根节点加 synchronized 锁,锁粒度更细,减少线程竞争。
  • 示例
    synchronized (node) {  // 锁住当前桶的头节点
        // 处理链表或红黑树的插入/删除操作
    }
    

3. 并发扩容机制

ConcurrentHashMap 支持多线程协同扩容,避免单线程扩容的性能瓶颈:

  1. 触发条件:当元素数量超过阈值(容量 × 负载因子)时触发扩容。
  2. 迁移策略
    • 将原数组分为多个区间(Stride),每个线程负责迁移一个区间的数据。
    • 使用 ForwardingNode 标记已迁移的桶,其他线程检测到此标记时会协助迁移。
  3. 并发迁移:多个线程可以并行迁移不同区间的数据,提高扩容效率。

4. 高效的 size 计算

  • 分片计数:通过 baseCountCounterCell[] 数组统计元素总数。
  • 避免竞争:更新计数时优先尝试 CAS 修改 baseCount,失败时使用 CounterCell 分片计数,减少线程竞争。
  • 最终一致性size() 方法返回的是近似值(遍历分片累加),但 mappingCount() 更精确。

5. 关键源码实现(简化)

putVal() 方法为例,流程如下:

final V putVal(K key, V value, boolean onlyIfAbsent) {
    // 1. 计算哈希值
    int hash = spread(key.hashCode());
    // 2. 遍历桶链表或红黑树
    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) {
            // 3. 桶为空时,CAS 插入新节点
            if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value)))
                break;
        }
        else if ((fh = f.hash) == MOVED)
            tab = helpTransfer(tab, f); // 协助扩容
        else {
            // 4. 桶非空时,锁住头节点处理链表/红黑树
            synchronized (f) {
                // 插入或更新节点
                if (tabAt(tab, i) == f) {
                    if (fh >= 0) { // 链表操作
                        // ... 遍历链表插入节点
                    } else if (f instanceof TreeBin) { // 红黑树操作
                        // ... 插入或更新树节点
                    }
                }
            }
        }
    }
    // 5. 检查是否需要扩容
    addCount(1L, binCount);
    return null;
}

6. JDK 7 vs. JDK 8 的差异

特性 JDK 7(分段锁) JDK 8(CAS + synchronized)
锁粒度 分段锁(Segment),每个段独立加锁 锁单个桶的头节点
数据结构 数组 + 链表 数组 + 链表 + 红黑树
并发度 由 Segment 数量决定(默认 16) 由桶数量决定,支持更高并发
扩容 段内扩容,锁住整个段 多线程协同扩容,无需全局锁
性能 分段锁竞争可能成为瓶颈 锁细化 + CAS 提升吞吐量

7. 适用场景

  • 高并发读写:适合多线程环境下的高频读写操作。
  • 替代 HashTable:性能远超 HashTable(HashTable 使用全局锁)。
  • 弱一致性迭代器:迭代器不会抛出 ConcurrentModificationException,但数据可能不是最新的。

8. 总结

ConcurrentHashMap 的核心设计思想是:

  1. 减少锁竞争:通过 CAS 和锁细化(锁单个桶)降低线程阻塞。
  2. 数据结构优化:链表转红黑树提升哈希冲突时的性能。
  3. 并发协作:多线程协同扩容和分片计数机制提高吞吐量。

这些机制使其成为高并发场景下最常用的线程安全 Map 实现。

你可能感兴趣的:(开发语言,java,后端)