JDK1.8的ConcurrentHashMap的put方法源码

一、JDK1.8的ConcurrentHashMap的put方法源码

ConcurrentHashMap 是 Java 并发包(java.util.concurrent)中的一个高性能线程安全哈希表实现。在 JDK 1.8 中,ConcurrentHashMap 的 put 方法是其核心方法之一,负责插入键值对并保证线程安全。

以下是 put 方法的详细源码解析,结合并发机制和设计思想


1、put 方法入口

  • 参数:

    • key:键。

    • value:值。

    • onlyIfAbsent:如果为 true,则仅在键不存在时插入

  • 返回值:如果键已存在,返回旧值;否则返回 null


2、putVal 方法解析


putVal 是 put 方法的核心实现,以下是其源码逐段解析:


(1) 参数校验

  • 设计意图:ConcurrentHashMap 不允许 null 键或值,因为在并发场景中,null 可能导致歧义(例如,无法区分“键不存在”和“键对应的值为 null”)


(2) 哈希计算

  • spread() 方法:对原始哈希码进行二次处理,确保哈希值均匀分布

  • 作用:

    • 通过异或高位和低位,减少哈希冲突

    • 通过 & HASH_BITS 确保哈希值为正数(负哈希用于标记特殊节点,如扩容时的 ForwardingNode)


(3) 自旋插入逻辑

  • 关键点:

    • CAS 无锁插入:若桶为空,直接通过 casTabAt 原子操作插入新节点,避免加锁

    • 协助扩容:若发现桶已被标记为 MOVED(ForwardingNode),当前线程会协助迁移数据,提升扩容效率


(4) initTable() 初始化哈希表

initTable() 是 ConcurrentHashMap 在 JDK 1.8 中用于初始化哈希表的核心方法。当 ConcurrentHashMap 第一次插入数据时,如果哈希表尚未初始化,

会调用 initTable() 方法创建并初始化哈希表。以下是 initTable() 方法的详细源码解析。


a、方法签名


b、方法解析


i、检查表是否已初始化

  • 条件:如果 table 为 null 或长度为 0,表示哈希表尚未初始化。

  • 作用:确保哈希表未初始化时才执行初始化逻辑。



ii、检查是否有其他线程正在初始化

  • sizeCtl 的作用:

    • sizeCtl 是 ConcurrentHashMap 的控制状态变量,用于控制初始化和扩容

    • 当 sizeCtl < 0 时,表示有其他线程正在初始化或扩容

  • Thread.yield():让出 CPU,等待其他线程完成初始化


iii、尝试 CAS 设置 sizeCtl 为 -1

  • CAS 操作:通过 CAS 将 sizeCtl 设置为 -1,表示当前线程正在初始化

  • 作用:确保只有一个线程执行初始化逻辑


iV、双重检查

  • 作用:防止其他线程已经完成初始化


iiV、计算初始容量

  • sc 的作用:

    • 如果 sc > 0,表示用户指定了初始容量

    • 否则,使用默认容量 DEFAULT_CAPACITY(默认值为 16)

  • 作用:确定哈希表的初始容量


iiiV、创建哈希表

  • 作用:创建长度为 n 的 Node 数组,并将其赋值给 table


iVi、计算扩容阈值

  • 计算逻辑:

    • n >>> 2 表示 n / 4

    • n - (n >>> 2) 表示 n * 0.75

  • 作用:设置扩容阈值(sizeCtl),当元素数量超过阈值时触发扩容


iVii、 恢复 sizeCtl 的值

  • 作用:初始化完成后,恢复 sizeCtl 的值,表示初始化完成


c、示例场景

假设 ConcurrentHashMap 第一次插入数据:

1、线程 A 检查 table,发现尚未初始化

2、线程 A 通过 CAS 将 sizeCtl 设置为 -1,表示正在初始化

3、线程 A 创建哈希表,并设置扩容阈值

4、线程 A 恢复 sizeCtl 的值,表示初始化完成

5、其他线程在初始化期间会调用 Thread.yield(),等待初始化完成


(5) helpTransfer(tab, f)方法源码

helpTransfer(tab, f) 是 ConcurrentHashMap 在 JDK 1.8 中用于协助扩容的核心方法之一。当线程在插入数据时发现当前桶正在迁移(即桶的头节点是 ForwardingNode),会调用 helpTransfer 方法协助其他线程完成数据迁移。以下是 helpTransfer 方法的详细源码解析:


a、方法签名


b、方法解析


i、 检查扩容状态

  • 条件:

    • 1、当前表 tab 不为空。

    • 2、当前桶的头节点 f 是 ForwardingNode(表示桶正在迁移)。

    • 3、ForwardingNode 的 nextTable 不为空(表示扩容正在进行)。

  • 作用:确认当前表正在扩容,且需要协助。


ii、 计算扩容戳

  • resizeStamp() 方法:生成一个扩容戳,用于标识当前扩容的状态。

  • 作用:

    • 1、Integer.numberOfLeadingZeros(n):计算表长度的前导零个数

    • 2、(1 << (RESIZE_STAMP_BITS - 1)):生成一个标志位

    • 3、最终结果是一个唯一的扩容戳,用于标识当前扩容


iii、 检查扩容是否仍在进行

  • 条件:

    • 1、nextTab 仍然是 nextTable(扩容目标表未改变)。

    • 2、table 仍然是 tab(当前表未改变)。

    • 3、sizeCtl 为负数(表示扩容仍在进行)。

  • 作用:确保当前扩容状态未发生变化


vi、判断是否需要协助

  • 条件:

    • 1、(sc >>> RESIZE_STAMP_SHIFT) != rs:扩容戳不匹配。

    • 2、sc == rs + 1:扩容已完成。

    • 3、sc == rs + MAX_RESIZERS:扩容线程数已达到最大值。

    • 4、transferIndex <= 0:所有桶已分配完毕。

  • 作用:如果满足任一条件,则无需协助,直接退出。


vii、尝试增加扩容线程数

  • CAS 操作:通过 CAS 将 sizeCtl 的值加 1,表示当前线程加入扩容

  • transfer() 方法:调用 transfer 方法协助迁移数据


viii、返回新表

  • 作用:返回扩容目标表 nextTab,供后续操作使用


c、transfer 方法的作用

transfer 方法是 ConcurrentHashMap 扩容的核心逻辑,负责将数据从旧表迁移到新表。以下是 transfer 方法的主要步骤:

  • 1、计算迁移范围:

    • 每个线程负责迁移一部分桶

    • 通过 transferIndex 分配迁移任务

  • 2、迁移数据:

    • 遍历旧表中的每个桶,将节点迁移到新表

    • 如果桶是链表,则拆分为高低链表;如果是红黑树,则拆分为高低树

  • 3、标记迁移完成:

    • 迁移完成后,将桶标记为 ForwardingNode


d、示例场景

假设 ConcurrentHashMap 正在扩容,线程 A 在插入数据时发现当前桶正在迁移,调用 helpTransfer 方法协助迁移:

1、线程 A 检查扩容状态,确认需要协助

2、线程 A 通过 CAS 增加扩容线程数,并调用 transfer 方法迁移数据

3、线程 A 完成迁移任务后,继续执行插入操作


(6) transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) 解析

transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) 是 ConcurrentHashMap 在 JDK 1.8 中用于扩容和数据迁移的核心方法。当哈希表中的元素数量超过阈值时,ConcurrentHashMap 会触发扩容,
并通过 transfer 方法将数据从旧表迁移到新表。transfer 方法支持多线程协作迁移数据,以提高扩容效率。


以下是 transfer 方法的详细源码解析


a、方法签名

    private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {
        int n = tab.length, stride;
        // 1. 计算每个线程负责的迁移范围(stride)
        if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)
            stride = MIN_TRANSFER_STRIDE; // 最小迁移范围
        // 2. 初始化新表(nextTab)
        if (nextTab == null) {
            try {
                Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n << 1]; // 新表容量为旧表的两倍
                nextTab = nt;
            } catch (Throwable ex) {
                sizeCtl = Integer.MAX_VALUE; // 扩容失败,恢复 sizeCtl
                return;
            }
            nextTable = nextTab;
            transferIndex = n; // 初始化迁移索引
        }
        int nextn = nextTab.length;
        // 3. 创建 ForwardingNode 节点,用于标记迁移中的桶
        ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab);
        boolean advance = true;
        boolean finishing = false; // 是否完成迁移
        // 4. 自旋迁移数据
        for (int i = 0, bound = 0;;) {
            Node<K,V> f; int fh;
            // 4.1 分配迁移任务
            while (advance) {
                int nextIndex, nextBound;
                if (--i >= bound || finishing)
                    advance = false;
                else if ((nextIndex = transferIndex) <= 0) {
                    i = -1;
                    advance = false;
                }
                else if (U.compareAndSwapInt(this, TRANSFERINDEX, nextIndex,
                          nextBound = (nextIndex > stride ? nextIndex - stride : 0))) {
                    bound = nextBound;
                    i = nextIndex - 1;
                    advance = false;
                }
            }
            // 4.2 检查迁移是否完成
            if (i < 0 || i >= n || i + n >= nextn) {
                int sc;
                if (finishing) {
                    nextTable = null;
                    table = nextTab;
                    sizeCtl = (n << 1) - (n >>> 1); // 更新 sizeCtl
                    return;
                }
                if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) {
                    if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT)
                        return;
                    finishing = advance = true;
                    i = n; // 重新检查所有桶
                }
            }
            // 4.3 迁移空桶
            else if ((f = tabAt(tab, i)) == null)
                advance = casTabAt(tab, i, null, fwd);
            // 4.4 迁移已处理的桶
            else if ((fh = f.hash) == MOVED)
                advance = true; // 跳过已迁移的桶
            // 4.5 迁移非空桶
            else {
                synchronized (f) {
                    if (tabAt(tab, i) == f) {
                        Node<K,V> ln, hn;
                        if (fh >= 0) { // 链表节点
                            // 拆分为高低链表
                            int runBit = fh & n;
                            Node<K,V> lastRun = f;
                            for (Node<K,V> p = f.next; p != null; p = p.next) {
                                int b = p.hash & n;
                                if (b != runBit) {
                                    runBit = b;
                                    lastRun = p;
                                }
                            }
                            if (runBit == 0) {
                                ln = lastRun;
                                hn = null;
                            }
                            else {
                                hn = lastRun;
                                ln = null;
                            }
                            for (Node<K,V> p = f; p != lastRun; p = p.next) {
                                int ph = p.hash; K pk = p.key; V pv = p.val;
                                if ((ph & n) == 0)
                                    ln = new Node<K,V>(ph, pk, pv, ln);
                                else
                                    hn = new Node<K,V>(ph, pk, pv, hn);
                            }
                            setTabAt(nextTab, i, ln);
                            setTabAt(nextTab, i + n, hn);
                            setTabAt(tab, i, fwd);
                            advance = true;
                        }
                        else if (f instanceof TreeBin) { // 红黑树节点
                            // 拆分为高低树
                            TreeBin<K,V> t = (TreeBin<K,V>)f;
                            TreeNode<K,V> lo = null, loTail = null;
                            TreeNode<K,V> hi = null, hiTail = null;
                            int lc = 0, hc = 0;
                            for (Node<K,V> e = t.first; e != null; e = e.next) {
                                int h = e.hash;
                                TreeNode<K,V> p = new TreeNode<K,V>
                                    (h, e.key, e.val, null, null);
                                if ((h & n) == 0) {
                                    if ((p.prev = loTail) == null)
                                        lo = p;
                                    else
                                        loTail.next = p;
                                    loTail = p;
                                    ++lc;
                                }
                                else {
                                    if ((p.prev = hiTail) == null)
                                        hi = p;
                                    else
                                        hiTail.next = p;
                                    hiTail = p;
                                    ++hc;
                                }
                            }
                            ln = (lc <= UNTREEIFY_THRESHOLD) ? untreeify(lo) :
                                (hc != 0) ? new TreeBin<K,V>(lo) : t;
                            hn = (hc <= UNTREEIFY_THRESHOLD) ? untreeify(hi) :
                                (lc != 0) ? new TreeBin<K,V>(hi) : t;
                            setTabAt(nextTab, i, ln);
                            setTabAt(nextTab, i + n, hn);
                            setTabAt(tab, i, fwd);
                            advance = true;
                        }
                    }
                }
            }
        }
    }


b、方法解析


i、计算迁移范围

  • stride:每个线程负责迁移的桶数量

  • NCPU:CPU 核心数

  • MIN_TRANSFER_STRIDE:最小迁移范围(默认 16)

  • 作用:根据 CPU 核心数动态分配迁移任务


ii、初始化新表

  • 作用:创建新表,容量为旧表的两倍


iii、创建 ForwardingNode

  • ForwardingNode:用于标记迁移中的桶,其他线程遇到时会协助迁移


Vi、自旋迁移数据

  • 关键点:

    • 分配迁移任务:通过 CAS 更新 transferIndex,分配迁移范围

    • 迁移空桶:将空桶标记为 ForwardingNode

    • 迁移非空桶:加锁迁移链表或红黑树


Vii、迁移链表

  • 作用:将链表拆分为高低链表,分别迁移到新表


Viii、迁移红黑树

  • 作用:将红黑树拆分为高低树,分别迁移到新表


c、总结

transfer 方法:用于扩容和数据迁移,支持多线程协作。

  • 核心机制:

    • 通过 CAS 分配迁移任务。

    • 使用 ForwardingNode 标记迁移中的桶。

    • 渐进式迁移,不影响读操作。

  • 适用场景:ConcurrentHashMap 的动态扩容


(7) 处理哈希冲突(加锁逻辑)

  • 锁策略:

    • 细粒度锁:仅锁住当前冲突的桶(头节点),其他桶仍可并发访问。

    • 双重检查:加锁后再次检查桶是否被修改,避免锁期间其他线程已修改结构

  • 链表与红黑树:

    • 链表插入采用尾插法(JDK 1.8 的改进,避免循环链表问题)。

    • 当链表长度超过 TREEIFY_THRESHOLD(默认8),会转换为红黑树。


(8) 链表转红黑树

  • 触发条件:单个桶的链表长度 ≥ TREEIFY_THRESHOLD(默认8)。

  • 实际转换条件:

    • 哈希表长度 ≥ MIN_TREEIFY_CAPACITY(默认64),否则优先扩容。


(9) 更新元素计数

  • addCount() 方法:使用类似 LongAdder 的分段计数机制,减少 CAS 竞争。

  • 设计目的:通过分散计数到多个 CounterCell,避免所有线程竞争同一变量


二、核心并发设计总结

1、无锁读:读操作直接访问 volatile 变量,无需同步。

2、CAS 写:桶为空时通过 CAS 插入,避免锁竞争。

3、细粒度锁:哈希冲突时仅锁住当前桶,锁粒度极小。

4、分段计数:使用 CounterCell 分散计数,减少 CAS 冲突。

5、协作扩容:多线程协同迁移数据,提升扩容效率。

posted @ 2025-02-14 21:16  jock_javaEE  阅读(108)  评论(0)    收藏  举报