ConcurrentHashMap 源码解析 - litter-fish/ReadSource GitHub Wiki

CAS 介绍

  • CAS 是比较交换的意思, Compare and swap
  • 使用3个基本操作数:内存地址V,旧的预期值A,要修改的新增B
  • 更新一个变量时,只有当旧的预期值A与地址V中的值相同时,才会将内存地址V对应的值修改为B

思想上说,Synchronize是一个悲观锁的实现方式,CAS是乐观锁的实现方式。 ConcurrentHashMap 是一个线程安全的Map集合,在高并发情况下可以保证线程安全。 ConcurrentHashMap 底层采用,数组+链表+红黑树实现,并通过CAS+Synchroized来保证并发更新

类继承关系

重要属性

// 集合最大容量,2的30次方
private static final int MAXIMUM_CAPACITY = 1 << 30;

// 集合的默认大小
private static final int DEFAULT_CAPACITY = 16;

// 默认负载因子
private static final float LOAD_FACTOR = 0.75f;

// 链表转为红黑树的阈值
static final int TREEIFY_THRESHOLD = 8;

// 红黑树转列表的阈值
static final int UNTREEIFY_THRESHOLD = 6;

static final int MOVED     = -1; // hash for forwarding nodes
static final int TREEBIN   = -2; // hash for roots of trees
static final int RESERVED  = -3; // hash for transient reservations
static final int HASH_BITS = 0x7fffffff; // usable bits of normal node hash

// 表初始化或者扩容的一个控制标识位
// 负数代表正在进行初始化或者扩容的操作
// -1 代表初始化
// -N 代表有n-1个线程在进行扩容操作
// 正数或者0表示没有进行初始化操作,这个数值表示初始化或者下一次要扩容的大小。

//transient 修饰的属性不会被序列化,volatile保证可见性
private transient volatile int sizeCtl;

构造函数

// 创建一个新的map,使用默认的大小16
public ConcurrentHashMap() {
}

// 使用传入的参数构造一个map
public ConcurrentHashMap(int initialCapacity) {
    if (initialCapacity < 0)
        throw new IllegalArgumentException();
    int cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ?
               MAXIMUM_CAPACITY :
               // tableSizeFor 将传入的参数转换为一个最靠近2的幂次方的值,比如9将转换为16
               tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1));
    this.sizeCtl = cap;
}

// 使用指定集合构造map
public ConcurrentHashMap(Map<? extends K, ? extends V> m) {
    this.sizeCtl = DEFAULT_CAPACITY;
    putAll(m);
}

// 指定初始容量和负载因子构造map
public ConcurrentHashMap(int initialCapacity, float loadFactor) {
    this(initialCapacity, loadFactor, 1);
}

// 指定初始化大小,负载因子和concurrentLevel并发更新线程的数量,也可以理解为segment的个数
public ConcurrentHashMap(int initialCapacity,
                         float loadFactor, int concurrencyLevel) {
    if (!(loadFactor > 0.0f) || initialCapacity < 0 || concurrencyLevel <= 0)
        throw new IllegalArgumentException();
    if (initialCapacity < concurrencyLevel)   // Use at least as many bins
        initialCapacity = concurrencyLevel;   // as estimated threads
    long size = (long)(1.0 + (long)initialCapacity / loadFactor);
    int cap = (size >= (long)MAXIMUM_CAPACITY) ?
        MAXIMUM_CAPACITY : tableSizeFor((int)size);
    this.sizeCtl = cap;
}

内部数据结构

static class Node<K,V> implements Map.Entry<K,V> {
    final int hash;
    final K key;
    volatile V val;
    volatile Node<K,V> next;

    Node(int hash, K key, V val, Node<K,V> next) {
        this.hash = hash;
        this.key = key;
        this.val = val;
        this.next = next;
    }

    // 省略getter方法

    public final boolean equals(Object o) {
        Object k, v, u; Map.Entry<?,?> e;
        return ((o instanceof Map.Entry) &&
                (k = (e = (Map.Entry<?,?>)o).getKey()) != null &&
                (v = e.getValue()) != null &&
                (k == key || k.equals(key)) &&
                (v == (u = val) || v.equals(u)));
    }

    /**
     * Virtualized support for map.get(); overridden in subclasses.
     */
    Node<K,V> find(int h, Object k) {
        Node<K,V> e = this;
        if (k != null) {
            do {
                K ek;
                if (e.hash == h &&
                    ((ek = e.key) == k || (ek != null && k.equals(ek))))
                    return e;
            } while ((e = e.next) != null);
        }
        return null;
    }
}

// 红黑树节点
static final class TreeNode<K,V> extends Node<K,V> {
    TreeNode<K,V> parent;  // red-black tree links
    TreeNode<K,V> left;
    TreeNode<K,V> right;
    TreeNode<K,V> prev;    // needed to unlink next upon deletion
    boolean red;

    TreeNode(int hash, K key, V val, Node<K,V> next,
             TreeNode<K,V> parent) {
        super(hash, key, val, next);
        this.parent = parent;
    }
}

// 红黑树的操作
static final class TreeBin<K,V> extends Node<K,V> {
    TreeNode<K,V> root;
    volatile TreeNode<K,V> first;
    volatile Thread waiter;
    volatile int lockState;
    // values for lockState
    static final int WRITER = 1; // set while holding write lock
    static final int WAITER = 2; // set when waiting for write lock
    static final int READER = 4; // increment value for setting read lock
}

// 连接两个table的节点类
static final class ForwardingNode<K,V> extends Node<K,V> {
    // 指向下一张表
    final Node<K,V>[] nextTable;
    ForwardingNode(Node<K,V>[] tab) {
        // 节点的key value next指针全部为null,它的hash值为-1
        super(MOVED, null, null, null);
        this.nextTable = tab;
    }
}

初始 table

private final Node<K,V>[] initTable() {
    Node<K,V>[] tab;
    int sc;
    while ((tab = table) == null || tab.length == 0) {
        if ((sc = sizeCtl) < 0)
            // sizeCtl < 0 表示有线程正在进行初始化操作,从运行状态变为就绪状态。
            Thread.yield(); // lost initialization race; just spin

        // 设置SIZECTL的值为-1,阻塞其他线程的操作
        // 该方法有四个参数
        // 第一个参数:需要改变的对象
        // 第二个参数:偏移量
        // 第三个参数:期待的值
        // 第四个参数:更新后的值
        else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
            try {
                // 再次检查是否有线程进行了初始化操作
                if ((tab = table) == null || tab.length == 0) {
                    int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
                    // 初始化Node对象数组
                    Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
                    table = tab = nt;
                    // sc的值设置为n的0.75倍
                    sc = n - (n >>> 2); // 相当于n*0.75
                }
            } finally {
                // 更改sizeCtl的值
                sizeCtl = sc;
            }
            break;
        }
    }
    return tab;
}

sizeCtl 属性在各个阶段的作用

  1. 创建集合对象时,sizeCtl只是用于记录初始容量大小
int cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ?
       MAXIMUM_CAPACITY :
       tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1));
this.sizeCtl = cap;
  1. 初始化过程,会将值设置为-1,表示已经有线程正在进行初始化操作,其他线程发现时会通过 yield 让出CPU资源,以便初始化操作尽快完成
U.compareAndSwapInt(this, SIZECTL, sc, -1)
  1. 初始化完成后, sizeCtl 用于记录触发扩容的值,即集合size * 负载因子
try {
    if ((tab = table) == null || tab.length == 0) {
        int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
        Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
        table = tab = nt;
        sc = n - (n >>> 2);
    }
} finally {
    sizeCtl = sc;
}
  1. 正在扩容,用于记录当前扩容并发数情况,sizeCtl 值为:((rs << RESIZE_STAMP_SHIFT) + 2) + (正在扩容的线程数)
//第一条扩容线程设置的某个特定基数
U.compareAndSwapInt(this, SIZECTL, sc, (rs << RESIZE_STAMP_SHIFT) + 2)
//后续线程加入扩容大军时每次加 1
U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)
//线程扩容完毕退出扩容操作时每次减 1
U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)

扩容方法

/**
 * 调用该扩容方法的地方有:
 * ConcurrentHashMap#addCount 向集合中插入新数据后更新容量计数时发现到达扩容阈值而触发的扩容
 * ConcurrentHashMap#helpTransfer 扩容状态下其他线程对集合进行插入、修改、删除、合并、compute 等操作时遇到 ForwardingNode 节点时触发的扩容
 * ConcurrentHashMap#tryPresize putAll批量插入或者插入后发现链表长度达到8个或以上,但数组长度为64以下时触发的扩容
 */
private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {
    // n 是tab的长度 , stride 初始值为0
    int n = tab.length, stride;
    // 计算每条线程处理的桶个数,每条线程处理的桶数量一样,如果CPU为单核,则使用一条线程处理所有桶
    // 每条线程至少处理16个桶,如果计算出来的结果少于16,则一条线程处理16个桶
    if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)
        stride = MIN_TRANSFER_STRIDE; // subdivide range
    if (nextTab == null) {            // 初始化新数组(原数组长度的2倍)
        try {
            // 构造一个容量是原来两倍的Node<K ,V> 类型数组
            Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n << 1];
            // 赋值
            nextTab = nt;
        } catch (Throwable ex) {      // try to cope with OOME
            sizeCtl = Integer.MAX_VALUE;
            return;
        }
        // 赋值
        nextTable = nextTab;
        // 将 transferIndex 指向最右边的桶,也就是数组索引下标最大的位置
        transferIndex = n;
    }
    // 获取新数组的长度
    int nextn = nextTab.length;
    // 新建一个占位对象,该占位对象的 hash 值为 -1 该占位对象存在时表示集合正在扩容状态,key、value、next 属性均为 null ,nextTable 属性指向扩容后的数组
    // 该占位对象主要有两个用途:
    //   1、占位作用,用于标识数组该位置的桶已经迁移完毕,处于扩容中的状态。
    //   2、作为一个转发的作用,扩容期间如果遇到查询操作,遇到转发节点,会把该查询操作转发到新的数组上去,不会阻塞查询操作。
    ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab);
    // advance 标识用于控制是否继续处理下一个桶,为 true 则表示已经处理完当前桶,可以继续迁移下一个桶的数据
    boolean advance = true;
    // finishing 标识用于控制扩容何时结束,该标识还有一个用途是最后一个扩容线程会负责重新检查一遍数组查看是否有遗漏的桶
    boolean finishing = false; // to ensure sweep before committing nextTab

    // 这个循环用于处理一个 stride 长度的任务,i 后面会被赋值为该 stride 内最大的下标,而 bound 后面会被赋值为该 stride 内最小的下标
    // 通过循环不断减小 i 的值,从右往左依次迁移桶上面的数据,直到 i 小于 bound 时结束该次长度为 stride 的迁移任务
    // 结束这次的任务后会通过外层 addCount、helpTransfer、tryPresize 方法的 while 循环达到继续领取其他任务的效果
    for (int i = 0, bound = 0;;) {
        // 创建两个变量,一个为Node<K,V> 类型,一个为int类型
        Node<K,V> f;
        int fh;

        while (advance) {
            int nextIndex, nextBound;
            // 每处理完一个hash桶就将 bound 进行减 1 操作
            if (--i >= bound || finishing)
                advance = false;
            // 将 transferIndex 的值赋值给 nextIndex ,并判断nextIndex的值是否小于等于0
            else if ((nextIndex = transferIndex) <= 0) {
                // transferIndex <= 0 说明数组的hash桶已被线程分配完毕,没有了待分配的hash桶,将 i 设置为 -1 ,后面的代码根据这个数值退出当前线的扩容操作
                i = -1;
                advance = false;
            }
            // 只有首次进入for循环才会进入这个判断里面去,设置 bound 和 i 的值,也就是领取到的迁移任务的数组区间
            else if (U.compareAndSwapInt
                     (this, TRANSFERINDEX, nextIndex,
                      nextBound = (nextIndex > stride ?
                                   nextIndex - stride : 0))) {
                bound = nextBound;
                i = nextIndex - 1;
                advance = false;
            }
        }
        if (i < 0 || i >= n || i + n >= nextn) {
            int sc;
            // 扩容结束后做后续工作,将 nextTable 设置为 null,表示扩容已结束,将 table 指向新数组,sizeCtl 设置为扩容阈值
            if (finishing) {
                // 清空nextTable
                nextTable = null;
                // 把nextTab 赋值给 table
                table = nextTab;
                // 阈值设置为容量的1.5倍
                sizeCtl = (n << 1) - (n >>> 1);
                return;
            }
            // 每当一条线程扩容结束就会更新一次 sizeCtl 的值,进行减 1 操作
            if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) {
                // (sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT 成立,说明该线程不是扩容大军里面的最后一条线程,直接return回到上层while循环
                if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT)
                    return;

                // (sc - 2) == resizeStamp(n) << RESIZE_STAMP_SHIFT 说明这条线程是最后一条扩容线程
                // 之所以能用这个来判断是否是最后一条线程,因为第一条扩容线程进行了如下操作:
                // U.compareAndSwapInt(this, SIZECTL, sc, (rs << RESIZE_STAMP_SHIFT) + 2)
                // 除了修改结束标识之外,还得设置 i = n; 以便重新检查一遍数组,防止有遗漏未成功迁移的桶
                finishing = advance = true;
                i = n; // recheck before commit
            }
        }
        // CAS算法获取某个数组节点,为空就设置为fwd
        else if ((f = tabAt(tab, i)) == null)
            // 遇到数组上空的位置直接放置一个占位对象,以便查询操作的转发和标识当前处于扩容状态
            advance = casTabAt(tab, i, null, fwd);
        // 如果某个节点的hash为-1,跳过
        else if ((fh = f.hash) == MOVED)
            // 数组上遇到hash值为MOVED,也就是 -1 的位置,说明该位置已经被其他线程迁移过了,将 advance 设置为 true ,以便继续往下一个桶检查并进行迁移操作
            advance = true; // already processed
        else {
            // 对头节点加锁,禁止其他线程进入
            synchronized (f) {
                if (tabAt(tab, i) == f) {

                    // 构造两个链表 ,将该节点的列表拆分为两个部分,一个是原链表的排列顺序,一个是反序
                    Node<K,V> ln, hn;
                    // 该节点为链表结构
                    if (fh >= 0) { // fh 当前节点的hash值   若 >= 0
                        int runBit = fh & n;
                        Node<K,V> lastRun = f;
                        // 遍历整条链表,找出 lastRun 节点
                        for (Node<K,V> p = f.next; p != null; p = p.next) {
                            int b = p.hash & n;
                            if (b != runBit) {
                                runBit = b;
                                lastRun = p;
                            }
                        }
                        // 根据 lastRun 节点的高位标识(0 或 1),首先将 lastRun设置为 ln 或者 hn 链的末尾部分节点,后续的节点使用头插法拼接
                        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方法调用的是 Unsafe 类的 putObjectVolatile 方法
                        // 使用 volatile 方式的 putObjectVolatile 方法,能够将数据直接更新回主内存,并使得其他线程工作内存的对应变量失效,达到各线程数据及时同步的效果
                        // 使用 volatile 的方式将 ln 链设置到新数组下标为 i 的位置上
                        setTabAt(nextTab, i, ln);
                        // 使用 volatile 的方式将 hn 链设置到新数组下标为 i + n(n为原数组长度) 的位置上
                        setTabAt(nextTab, i + n, hn);
                        // 迁移完成后使用 volatile 的方式将占位对象设置到该 hash 桶上,该占位对象的用途是标识该hash桶已被处理过,以及查询请求的转发作用
                        setTabAt(tab, i, fwd);
                        // advance 设置为 true 表示当前 hash 桶已处理完,可以继续处理下一个 hash 桶
                        advance = true;
                    }
                    // 对TreeBin对象进行处理,过程与上面有些类似
                    // 也把节点分类,分别插入到lo和hi为头节点的链表中
                    else if (f instanceof TreeBin) {
                        // lo 为低位链表头结点,loTail 为低位链表尾结点,hi 和 hiTail 为高位链表头尾结点
                        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循环以链表方式遍历整棵红黑树,使用尾插法拼接 ln 和 hn 链表
                        for (Node<K,V> e = t.first; e != null; e = e.next) {
                            int h = e.hash;
                            // 这里面形成的是以 TreeNode 为节点的链表
                            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;
                            }
                        }

                        // 形成中间链表后会先判断是否需要转换为红黑树:
                        // 1、如果符合条件则直接将 TreeNode 链表转为红黑树,再设置到新数组中去
                        // 2、如果不符合条件则将 TreeNode 转换为普通的 Node 节点,再将该普通链表设置到新数组中去
                        // (hc != 0) ? new TreeBin<K,V>(lo) : t 这行代码的用意在于,如果原来的红黑树没有被拆分成两份,那么迁移后它依旧是红黑树,可以直接使用原来的 TreeBin 对象
                        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方法调用的是 Unsafe 类的 putObjectVolatile 方法
                        // 使用 volatile 方式的 putObjectVolatile 方法,能够将数据直接更新回主内存,并使得其他线程工作内存的对应变量失效,达到各线程数据及时同步的效果
                        // 使用 volatile 的方式将 ln 链设置到新数组下标为 i 的位置上
                        setTabAt(nextTab, i, ln);
                        // 使用 volatile 的方式将 hn 链设置到新数组下标为 i + n(n为原数组长度) 的位置上
                        setTabAt(nextTab, i + n, hn);
                        // 迁移完成后使用 volatile 的方式将占位对象设置到该 hash 桶上,该占位对象的用途是标识该hash桶已被处理过,以及查询请求的转发作用
                        setTabAt(tab, i, fwd);
                        // advance 设置为 true 表示当前 hash 桶已处理完,可以继续处理下一个 hash 桶
                        advance = true;
                    }
                }
            }
        }
    }
}

新增元素触发扩容

// 扩容触发
// 新增元素时,也就是在调用 putVal 方法后,为了通用,增加了个 check 入参,用于指定是否可能会出现扩容的情况
// check >= 0 即为可能出现扩容的情况,例如 putVal方法中的调用
private final void addCount(long x, int check) {
    CounterCell[] as; long b, s;
    if ((as = counterCells) != null ||
        !U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) {
        CounterCell a; long v; int m;
        boolean uncontended = true;
        if (as == null || (m = as.length - 1) < 0 ||
            (a = as[ThreadLocalRandom.getProbe() & m]) == null ||
            !(uncontended =
              U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) {
            fullAddCount(x, uncontended);
            return;
        }
        if (check <= 1)
            return;
        s = sumCount();
    }
    if (check >= 0) {
        Node<K,V>[] tab, nt; int n, sc;
        // 检查当前集合元素个数 s 是否达到扩容阈值 sizeCtl ,扩容时 sizeCtl 为负数,依旧成立,同时还得满足数组非空且数组长度不能大于允许的数组最大长度这两个条件才能继续
        // 这个 while 循环除了判断是否达到阈值从而进行扩容操作之外还有一个作用就是当一条线程完成自己的迁移任务后,如果集合还在扩容,则会继续循环,继续加入扩容大军,申请后面的迁移任务
        while (s >= (long)(sc = sizeCtl) && (tab = table) != null &&
               (n = tab.length) < MAXIMUM_CAPACITY) {
            int rs = resizeStamp(n);
            // sc < 0 说明集合正在扩容当中
            if (sc < 0) {
                // 判断扩容是否结束或者并发扩容线程数是否已达最大值,如果是的话直接结束while循环
                if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
                    sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||
                    transferIndex <= 0)
                    break;
                // 扩容还未结束,并且允许扩容线程加入,此时加入扩容大军中
                if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))
                    transfer(tab, nt);
            }
            // 如果集合还未处于扩容状态中,则进入扩容方法,并首先初始化 nextTab 数组,也就是新数组
            // (rs << RESIZE_STAMP_SHIFT) + 2 为首个扩容线程所设置的特定值,后面扩容时会根据线程是否为这个值来确定是否为最后一个线程
            else if (U.compareAndSwapInt(this, SIZECTL, sc,
                                         (rs << RESIZE_STAMP_SHIFT) + 2))
                transfer(tab, null);
            s = sumCount();
        }
    }
}

其他线程对集合进行插入、修改、删除、合并、compute等操作时遇到 ForwardingNode 节点会调用该帮助扩容方法

// 扩容状态下其他线程对集合进行插入、修改、删除、合并、compute等操作时遇到 ForwardingNode 节点会调用该帮助扩容方法
final Node<K,V>[] helpTransfer(Node<K,V>[] tab, Node<K,V> f) {
    Node<K,V>[] nextTab; int sc;
    if (tab != null && (f instanceof ForwardingNode) &&
        (nextTab = ((ForwardingNode<K,V>)f).nextTable) != null) {
        int rs = resizeStamp(tab.length);
        while (nextTab == nextTable && table == tab &&
               (sc = sizeCtl) < 0) {
            if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
                sc == rs + MAX_RESIZERS || transferIndex <= 0)
                break;
            if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) {
                transfer(tab, nextTab);
                break;
            }
        }
        return nextTab;
    }
    return table;
}

putAll批量插入或者插入节点后发现链表长度达到8个或以上,但数组长度为64以下时触发

// putAll批量插入或者插入节点后发现链表长度达到8个或以上,但数组长度为64以下时触发的扩容会调用到这个方法
private final void tryPresize(int size) {
    int c = (size >= (MAXIMUM_CAPACITY >>> 1)) ? MAXIMUM_CAPACITY :
        tableSizeFor(size + (size >>> 1) + 1);
    int sc;
    // 如果不满足条件,也就是 sizeCtl < 0 ,说明有其他线程正在扩容当中,这里也就不需要自己去扩容了,结束该方法
    while ((sc = sizeCtl) >= 0) {
        Node<K,V>[] tab = table; int n;
        // 如果数组初始化则进行初始化,这个选项主要是为批量插入操作方法 putAll 提供的
        if (tab == null || (n = tab.length) == 0) {
            n = (sc > c) ? sc : c;
            // 初始化时将 sizeCtl 设置为 -1 ,保证单线程初始化
            if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
                try {
                    if (table == tab) {
                        @SuppressWarnings("unchecked")
                        Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
                        table = nt;
                        sc = n - (n >>> 2);
                    }
                } finally {
                    // 初始化完成后 sizeCtl 用于记录当前集合的负载容量值,也就是触发集合扩容的阈值
                    sizeCtl = sc;
                }
            }
        }
        else if (c <= sc || n >= MAXIMUM_CAPACITY)
            break;
        // 插入节点后发现链表长度达到8个或以上,但数组长度为64以下时触发的扩容会进入到下面这个 else if 分支
        else if (tab == table) {
            int rs = resizeStamp(n);
            // 下面的内容基本跟上面 addCount 方法的 while 循环内部一致,可以参考上面的注释
            if (sc < 0) {
                Node<K,V>[] nt;
                if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
                    sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||
                    transferIndex <= 0)
                    break;
                if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))
                    transfer(tab, nt);
            }
            else if (U.compareAndSwapInt(this, SIZECTL, sc,
                                         (rs << RESIZE_STAMP_SHIFT) + 2))
                transfer(tab, null);
        }
    }
}

注意:桶上链表长度达到 8 个或者以上,并且数组长度为 64 以下时只会触发扩容而不会将链表转为红黑树 。

扩容过程图解

触发扩容的操作 6d0241b0d3d002ef9820d75722fc64d1.png

CPU核数与迁移任务hash桶数量分配的关系 f1fdde5770e3197ed8501716bae89065.png

单线程下线程的任务分配与迁移操作 9c5c6c47458011ce5c69cfbff546addf.png

多线程分配任务 23435f97c8279beef7b8f359034240cf.png

普通链表迁移 a1d2ddeaeb015d87c97e832e7c87deb5.png

lastRun 节点 184dcd0ecf6b9046147e50b7bfac02c5.png

红黑树迁移 08bdbc46abef9b8240a3750261afa879.png

hash桶迁移中以及迁移后处理存取请求 d802010afbd0d56323fac2d2a4a0af53.png

多线程迁移任务完成后的操作 db3d521f91e9092de3de8cdca4cb5668.png aa5a067a56955ee660d9215358f01a67.png

put 操作

final V putVal(K key, V value, boolean onlyIfAbsent) {
    // ConcurrentHashMap不允许键或者值为null
    if (key == null || value == null) throw new NullPointerException();
    // 散列后再次散列, 让数据均匀分布,减少碰撞次数
    int hash = spread(key.hashCode());
    int binCount = 0;
    for (Node<K,V>[] tab = table;;) {
        // 创建一个Node类型的变量f , int 类型的变量 n i fh
        Node<K,V> f; int n, i, fh;
        // 判断tab是否为null  ,是否进行了初始化操作,如果没有执行初始化,执行初始化操作
        if (tab == null || (n = tab.length) == 0)
            tab = initTable();
        // tabAt 获取值
        else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
            // 添加到table中
            if (casTabAt(tab, i, null,
                         new Node<K,V>(hash, key, value, null)))
                break;                   // no lock when adding to empty bin
        }
        else if ((fh = f.hash) == MOVED)
            tab = helpTransfer(tab, f);
        else {
            V oldVal = null;
            synchronized (f) {
                if (tabAt(tab, i) == f) {
                    if (fh >= 0) {
                        binCount = 1;
                        for (Node<K,V> e = f;; ++binCount) {
                            K ek;
                            // key 相等,使用新值替换旧值
                            if (e.hash == hash &&
                                ((ek = e.key) == key ||
                                 (ek != null && key.equals(ek)))) {
                                oldVal = e.val;
                                if (!onlyIfAbsent)
                                    e.val = value;
                                break;
                            }
                            Node<K,V> pred = e;
                            // 放在链表的尾部
                            if ((e = e.next) == null) {
                                pred.next = new Node<K,V>(hash, key,
                                                          value, null);
                                break;
                            }
                        }
                    }
                    // 红黑树替换
                    else if (f instanceof TreeBin) {
                        Node<K,V> p;
                        binCount = 2;
                        if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
                                                       value)) != null) {
                            oldVal = p.val;
                            if (!onlyIfAbsent)
                                p.val = value;
                        }
                    }
                }
            }
            if (binCount != 0) {
                if (binCount >= TREEIFY_THRESHOLD)
                    treeifyBin(tab, i);
                if (oldVal != null)
                    return oldVal;
                break;
            }
        }
    }
    addCount(1L, binCount);
    return null;
}

static final int spread(int h) {
    // 对hashCode进行高16位和低16位异或运算,然后再与 0x7fffffff 进行与运算
    // 高低位异或运算可以保证haahCode的每一位都可以参与运算,从而使运算的结果更加均匀的分布在不同的区域,在计算table位置时可以减少冲突,提高效率
    // 得出运算结果后再和 0x7fffffff 与运算,其目的是保证每次运算结果都是一个正数
    return (h ^ (h >>> 16)) & HASH_BITS;
}

static final int HASH_BITS = 0x7fffffff; // usable bits of normal node hash

// 获取索引i处Node
static final <K,V> Node<K,V> tabAt(Node<K,V>[] tab, int i) {
    return (Node<K,V>)U.getObjectVolatile(tab, ((long)i << ASHIFT) + ABASE);
}

// 利用CAS算法设置i位置上的Node节点(将c和tab[i]比较,相同则插入v)。
static final <K,V> boolean casTabAt(Node<K,V>[] tab, int i,
                                    Node<K,V> c, Node<K,V> v) {
    return U.compareAndSwapObject(tab, ((long)i << ASHIFT) + ABASE, c, v);
}

// 设置节点位置的值,仅在上锁区被调用
static final <K,V> void setTabAt(Node<K,V>[] tab, int i, Node<K,V> v) {
    U.putObjectVolatile(tab, ((long)i << ASHIFT) + ABASE, v);
}

Get 方法

public V get(Object key) {
    Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
    // 根据key的hashcode计算hash值在和长度n - 1进行与运算,得到下标值
    int h = spread(key.hashCode());
    if ((tab = table) != null && (n = tab.length) > 0 &&
        (e = tabAt(tab, (n - 1) & h)) != null) {
        if ((eh = e.hash) == h) {
            if ((ek = e.key) == key || (ek != null && key.equals(ek)))
                return e.val;
        }
        // eh< 0 表示红黑树节点
        else if (eh < 0)
            return (p = e.find(h, key)) != null ? p.val : null;
        // 链表遍历
        while ((e = e.next) != null) {
            if (e.hash == h &&
                ((ek = e.key) == key || (ek != null && key.equals(ek))))
                return e.val;
        }
    }
    return null;
}

删除操作

public V remove(Object key) {
    return replaceNode(key, null, null);
}

// 当value为空,则删除,否则根据cv进行替换
final V replaceNode(Object key, V value, Object cv) {
    int hash = spread(key.hashCode());
    for (Node<K,V>[] tab = table;;) {
        Node<K,V> f; int n, i, fh;
        // 数组不存在,或对应的桶为空
        if (tab == null || (n = tab.length) == 0 ||
            (f = tabAt(tab, i = (n - 1) & hash)) == null)
            break;
        // 暂停帮助扩容
        else if ((fh = f.hash) == MOVED)
            tab = helpTransfer(tab, f);
        // 加锁,删除
        else {
            V oldVal = null;
            boolean validated = false;
            synchronized (f) {
                if (tabAt(tab, i) == f) {
                    // 节点hash >= 0 表示是一个链表
                    if (fh >= 0) {
                        validated = true;
                        for (Node<K,V> e = f, pred = null;;) {
                            K ek;
                            // 链表中定位到删除节点
                            if (e.hash == hash &&
                                ((ek = e.key) == key ||
                                 (ek != null && key.equals(ek)))) {
                                V ev = e.val;

                                // 根据传入的参数确定是否替换节点值还是删除节点
                                if (cv == null || cv == ev ||
                                    (ev != null && cv.equals(ev))) {
                                    oldVal = ev;
                                    // 替换
                                    if (value != null)
                                        e.val = value;
                                    // 删除
                                    else if (pred != null)
                                        pred.next = e.next;
                                    // 删除节点位于桶的头节点,改变桶头节点
                                    else
                                        setTabAt(tab, i, e.next);
                                }
                                break;
                            }
                            pred = e;
                            if ((e = e.next) == null)
                                break;
                        }
                    }
                    // 红黑树的删除
                    else if (f instanceof TreeBin) {
                        validated = true;
                        TreeBin<K,V> t = (TreeBin<K,V>)f;
                        TreeNode<K,V> r, p;
                        if ((r = t.root) != null &&
                            (p = r.findTreeNode(hash, key, null)) != null) {
                            V pv = p.val;
                            if (cv == null || cv == pv ||
                                (pv != null && cv.equals(pv))) {
                                oldVal = pv;
                                if (value != null)
                                    p.val = value;
                                else if (t.removeTreeNode(p))
                                    setTabAt(tab, i, untreeify(t.first));
                            }
                        }
                    }
                }
            }
            // 找到删除的节点,删除后需要进行扩容操作
            if (validated) {
                if (oldVal != null) {
                    if (value == null)
                        addCount(-1L, -1);
                    return oldVal;
                }
                break;
            }
        }
    }
    return null;
}

参考: https://blog.csdn.net/ZOKEKAI/article/details/90051567

⚠️ **GitHub.com Fallback** ⚠️