ConcurrentHashMap源码添加删除操作

  • ConcurrentHashMap源码学习
  • 一些常量
  • Unsafe获取内存中的值
  • 添加操作
  • put方法
  • putVal方法
  • spread方法
  • initTable方法
  • tabAt方法
  • casTabAt
  • helpTransfer方法
  • transfer方法
  • addCount方法
  • sumCount方法
  • fullAddCount方法
  • putTreeVal方法
  • lockRoot方法
  • unlockRoot方法
  • contendedLock方法
  • balanceInsertion方法
  • findTreeNode方法
  • treeifyBin方法
  • tryPresize方法
  • TreeBin方法
  • setTabAt方法
  • 删除操作
  • remove方法
  • replaceNode方法
  • removeTreeNode方法
  • balanceDeletion方法
  • untreeify方法
  • get操作
  • get方法
  • find方法


ConcurrentHashMap源码学习

一些常量

最大数组大小,为什么要Integer.MAX_VALUE-8呢,因为这八个字节要拿来放元数据,也就是数组的固有属性,比如length

static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

默认并发量,也就是最多多少个线程同时操作

private static final int DEFAULT_CONCURRENCY_LEVEL = 16;

最少线程任务量,这个值在进行扩容迁移时会用到,多个线程同时扩容,需要分配给每个线程的最少任务量,也就是桶的个数,最少为16

private static final int MIN_TRANSFER_STRIDE = 16;

SizeCtl中用于生成扩容表示戳记的位数。

private static int RESIZE_STAMP_BITS = 16;

可以帮助扩容的最大线程数

private static final int MAX_RESIZERS = (1 << (32 - RESIZE_STAMP_BITS)) - 1;

用于记录sizeCtl中的大小戳的位移位

private static final int RESIZE_STAMP_SHIFT = 32 - RESIZE_STAMP_BITS;

各种不同的hash值

//代表是一个fwd节点
static final int MOVED = -1; //ForwardingNode
//代表是一个红黑树节点
static final int TREEBIN = -2; // TreeBin
//占位节点的hash,用在computeIfAbsent方法中
static final int RESERVED = -3; // ReservationNode
//用来使得求得的hash一定是正数的hash值
static final int HASH_BITS = 0x7fffffff; // usable bits of normal node hash

cpu的核数

static final int NCPU = Runtime.getRuntime().availableProcessors();

基本数量计数器,记录表的元素个数

private transient volatile long baseCount;

表初始化和调整大小控件。
如果为负,则正在初始化该表或调整其大小:-1表示初始化,否则为-(1+活动的调整大小线程数)。
否则,当table为NULL时,保存创建时使用的初始表大小,或0表示默认大小。
初始化后,保存要根据其调整表大小的扩容阈值。

private transient volatile int sizeCtl;

迁移时的索引,初始在表的最后面,不断减一直到数据迁移完成

private transient volatile int transferIndex;

判断是否能够修改CounterCell数组的标志位,0,可修改,1,不可修改

private transient volatile int cellsBusy;

保存数组元素数量的数组,每个元素有一个value值,把数组所有元素的value相加再加上baseCount就得到了数组的元素个数

private transient volatile ConcurrentHashMap.CounterCell[] counterCells;

Unsafe获取内存中的值

// Unsafe mechanics
private static final sun.misc.Unsafe U;
private static final long SIZECTL;
private static final long TRANSFERINDEX;
private static final long BASECOUNT;
private static final long CELLSBUSY;
private static final long CELLVALUE;
private static final long ABASE;
private static final int ASHIFT;

static {
    try {
        U = sun.misc.Unsafe.getUnsafe();
        Class<?> k = ConcurrentHashMap.class;
        SIZECTL = U.objectFieldOffset
                (k.getDeclaredField("sizeCtl"));
        TRANSFERINDEX = U.objectFieldOffset
                (k.getDeclaredField("transferIndex"));
        BASECOUNT = U.objectFieldOffset
                (k.getDeclaredField("baseCount"));
        CELLSBUSY = U.objectFieldOffset
                (k.getDeclaredField("cellsBusy"));
        Class<?> ck = ConcurrentHashMap.CounterCell.class;
        CELLVALUE = U.objectFieldOffset
                (ck.getDeclaredField("value"));
        Class<?> ak = ConcurrentHashMap.Node[].class;
        //数组的初始内存地址
        ABASE = U.arrayBaseOffset(ak);
        //一个元素的大小
        int scale = U.arrayIndexScale(ak);
        if ((scale & (scale - 1)) != 0)
            throw new Error("data type scale not a power of two");
        //返回数组中一个元素所占的内存大小
        ASHIFT = 31 - Integer.numberOfLeadingZeros(scale);
    } catch (Exception e) {
        throw new Error(e);
    }
}

添加操作

put方法

添加元素

public V put(K key, V value) {
    return putVal(key, value, false);
}

putVal方法

添加元素,首先求得元素所在的桶,也就是hash值

然后遍历这个桶,若是空的,就初始化这个桶

若表不为空且当前桶位置是空的,说明这个桶位置还没有放元素,就新建一个节点放上去结束

如果判断hash值为MOVED,说明有线程正在扩容,进行协助扩容

否则就开始真正的插入元素,首先锁住当前桶,再次判断当前索引位置的值没有变

判断hash值大于0,说明在这个桶上的是一条链表,那就遍历链表看能不能找到key对应的节点,有的话就修改值然后结束,没有就创建一个新节点放在链表尾部

判断hash值小于0,说明是一颗红黑树,调用红黑树的插入方法插入元素,插入后返回插入的节点,如果可以改变值的话就把值改变

最后判断创建的链表是不是大于树化阈值,大于就把链表树化,然后将表的元素个数加一

final V putVal(K key, V value, boolean onlyIfAbsent) {
    if (key == null || value == null) throw new NullPointerException();
    //求得key的hash,一定是正数
    int hash = spread(key.hashCode());
    //链表的长度
    int binCount = 0;
    //遍历当前桶
    for(ConcurrentHashMap.Node<K, V>[] tab = table; ; ) {
        ConcurrentHashMap.Node<K, V> f;
        int n, i, fh;
        //表的长度为0或者是空,初始化表
        if (tab == null || (n = tab.length) == 0)
            tab = initTable();
        //此时内存中当前索引位置的值为空
        else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
            //此时说明索引位置还没有放东西
            //比较内存中对应索引的值是不是真的是空的,
            // 如果为空,就新建一个节点放到对应位置
            if (casTabAt(tab, i, null,
                    new ConcurrentHashMap.Node<K, V>(hash, key, value, null)))
                break;
        } else if ((fh = f.hash) == MOVED)
            // ForwardingNode节点的hash值会是-1
            // 所以这里说明这是一个ForwardingNode节点
            //说明有线程正在扩容数组,则一起进行扩容操作
            tab = helpTransfer(tab, f);
        else {
            //开始准备在当前桶插入值
            V oldVal = null;
            synchronized (f) {
                //锁住当前桶对象
                if (tabAt(tab, i) == f) {
                    if (fh >= 0) {
                        //hash>0说明是一条链表
                        //链表长度
                        binCount = 1;
                        for(ConcurrentHashMap.Node<K, V> e = f; ; ++binCount) {
                            K ek;
                            //hash相同key相同,则找到了,然后替换旧值
                            if (e.hash == hash &&
                                    ((ek = e.key) == key ||
                                            (ek != null && key.equals(ek)))) {
                                oldVal = e.val;
                                if (!onlyIfAbsent)
                                    e.val = value;
                                break;
                            }
                            //遍历完了都还没找到,那就创建一个新节点放到链表的尾部
                            ConcurrentHashMap.Node<K, V> pred = e;
                            if ((e = e.next) == null) {
                                pred.next = new ConcurrentHashMap.Node<K, V>(hash, key,
                                        value, null);
                                break;
                            }
                        }
                    } else if (f instanceof ConcurrentHashMap.TreeBin) {
                        //说明是一棵红黑树
                        ConcurrentHashMap.Node<K, V> p;
                        binCount = 2;
                        //以红黑树的方式插入新值
                        if ((p = ((ConcurrentHashMap.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;
            }
        }
    }
    //将数组的大小加1,因为添加了一个元素
    addCount(1L, binCount);
    return null;
}

spread方法

求hash值,这个值一定是正数

static final int spread(int h) {
    //HASH_BITS的二进制形式:0111 1111 1111 1111 1111 1111 1111 1111
    //最高位为0,其他是1,所以会把算出来的hash值置为正数,永远不会为负数
    return (h ^ (h >>> 16)) & HASH_BITS;
}

initTable方法

初始化表格,当表格为空或者长度为0进行初始化,判断是否正在扩容,正在扩容就阻塞当前线程,没有的话就把sc置为-1,表示正在初始化,创建一个大小为指定容量的数组,并修改扩容阈值为0.75n

private final ConcurrentHashMap.Node<K, V>[] initTable() {
    ConcurrentHashMap.Node<K, V>[] tab;
    int sc;
    while((tab = table) == null || tab.length == 0) {
        if ((sc = sizeCtl) < 0)
            //如果为负(sc<0),则正在初始化该表或调整其大小:-1表示初始化,否则为-(1+活动的调整大小线程数)。
            //否则,当table为NULL时,保存创建时使用的初始表大小,或0表示默认大小。
            //初始化后,保存下一个要扩容的阈值。
            Thread.yield(); // lost initialization race; just spin
        //yield会让出执行权并与其他线程共同竞争执行权
        //yield()应该做的是让当前运行线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会。
        // 因此,使用yield()的目的是让相同优先级的线程之间能适当的轮转执行。
        // 但是,实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。
        else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
            //方法的作用是,读取传入对象o在内存中偏移量为offset位置的值与期望值expected作比较。
            //相等就把x值赋值给offset位置的值。方法返回true。
            //不相等,就取消赋值,方法返回false。
            //这也是CAS的思想,及比较并交换。用于保证并发时的无锁并发的安全性。
            //这里如果相等就把-1给到SIZECTL,保证其他线程进来时就走上面那个if
            //因为现在表都还没有初始化
            try {
                //进行double check,防止其他线程已经改变表的值
                if ((tab = table) == null || tab.length == 0) {
                    //判断size是否大于0,等于0就给个默认初始化容量,
                    // 大于说明有传参,那就用传进来的参数
                    int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
                    //此时才会去新建一个表,然后赋值给表,也就是说concurrrenthashmap是懒惰初始化的
                    @SuppressWarnings("unchecked")
                    ConcurrentHashMap.Node<K, V>[] nt = (ConcurrentHashMap.Node<K, V>[]) new ConcurrentHashMap.Node<?, ?>[n];
                    table = tab = nt;
                    //sc变成原来的0.75倍,刚好是装载因子得到的最大扩容阈值
                    sc = n - (n >>> 2);//相当于0.75*sc
                }
            } finally {
                //重新赋值sizeCtl
                sizeCtl = sc;
            }
            break;
        }
    }
    return tab;
}

tabAt方法

//寻找指定数组在内存中i位置的数据。
@SuppressWarnings("unchecked")
static final <K, V> ConcurrentHashMap.Node<K, V> tabAt(ConcurrentHashMap.Node<K, V>[] tab, int i) {
    //ABASE,数组在内存中的起始地址,ASHIFT数组中每个元素所占的大小,例如int占4个字节
    /*
    * int a[]={3,4,5};
    * ABASE假设是0x7888
    * ASHIFT就是2,因为整形占4个字节,4的二进制表示形式是100,所以ASHIFT是2
    * i << ASHIFT,假设i是2,代表我要拿a[2],在内存中就是 数组起始地址+偏移地址
    * 所以就是 2*4+0x7888;
    * 因为我要拿第2个元素,每个元素占四个字节,所以要乘2
    * 所以也就是 ((long) i << ASHIFT) + ABASE要这么算的原因
    * */
    return (ConcurrentHashMap.Node<K, V>) U.getObjectVolatile(tab, ((long) i << ASHIFT) + ABASE);
}

casTabAt

static final <K, V> boolean casTabAt(ConcurrentHashMap.Node<K, V>[] tab, int i,
                                     ConcurrentHashMap.Node<K, V> c, ConcurrentHashMap.Node<K, V> v) {
    //obj :包含要修改的字段对象;
    //offset :字段在对象内的偏移量;
    //expect : 字段的期望值;
    //update :如果该字段的值等于字段的期望值,用于更新字段的新值;
    //通过我们传入的字段在对象中的偏移量来获取到字段的地址(对象首地址 + 字段在对象中的偏移量);
    //然后调用 CompareAndSwap 方法比较字段的地址是否与我们期望的地址相等,如果相等则使用我们传入的新地址更新字段的地址;
    return U.compareAndSwapObject(tab, ((long) i << ASHIFT) + ABASE, c, v);
}

helpTransfer方法

协助扩容,先判断是不是扩容完成了,完成了就什么也不做,直接退出,否则将sc+1,表示增加了一个协助扩容的线程,然后协助扩容

/**
 * 如果正在调整大小,则帮助扩容。
 */
final ConcurrentHashMap.Node<K, V>[] helpTransfer(ConcurrentHashMap.Node<K, V>[] tab, ConcurrentHashMap.Node<K, V> f) {
    ConcurrentHashMap.Node<K, V>[] nextTab;
    int sc;
    //表不是空的且f是转移节点,且f的nextTable可用
    if (tab != null && (f instanceof ConcurrentHashMap.ForwardingNode) &&
            (nextTab = ((ConcurrentHashMap.ForwardingNode<K, V>) f).nextTable) != null) {
        //根据length得到一个标识符
        int rs = resizeStamp(tab.length);
        while(nextTab == nextTable && table == tab &&
                (sc = sizeCtl) < 0) {
            //-(1+活动的调整大小线程数)。
            //条件一:(sc >>> RESIZE_STAMP_SHIFT) != rs
            //true->说明当前线程获取到的扩容唯一标识戳 非 本批次扩容
            //false->说明当前线程获取到的扩容唯一标识戳 是 本批次扩容
            //条件二: JDK1.8 中有bug jira已经提出来了 其实想表达的是 =  sc == (rs << 16 ) + 1
            // true-> 表示扩容完毕,当前线程不需要再参与进来了
            //false->扩容还在进行中,当前线程可以参与
            //条件三:JDK1.8 中有bug jira已经提出来了 其实想表达的是 = sc == (rs<<16) + MAX_RESIZERS
            //条件四:transferIndex <= 0
            //      true->说明map对象全局范围内的任务已经分配完了,当前线程进去也没活干..
            //      false->还有任务可以分配。
            //总之一句话,这里表示的是扩容已经结束了,所以直接break退出了
            if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
                    sc == rs + MAX_RESIZERS || transferIndex <= 0)
                break;
            //如果扩容还没结束,就加入一起扩容,所以sc+1
            if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) {
                transfer(tab, nextTab);
                break;
            }
        }
        return nextTab;
    }
    return table;
}

transfer方法

扩容,首先求出一个线程的最小任务量stride,这个值最少为16,然后判断是否是第一个来扩容的,是的话就初始化扩容的新表,大小为原来的2倍

不是就去循环里领取扩容任务,任务分配方式是每个线程处理一个stride任务量,直到最后的桶的个数已经小于stride,就把剩下的桶全部给来的这个线程处理,然后迁移是从原表倒着迁移,每次迁移一个桶的元素

迁移方式:如果桶是空的,就放一个fwd节点,然后去下一个桶,如果当前节点的hash=moved,说明有其他线程在迁移,自己就不要做了,去下一个桶,当这些都没有,就锁住桶,准备迁移,迁移方式:判断是链表还是红黑树,链表,就把链表分链,然后低链表放在新表的原索引处,高链表就放在新表的 原索引+原来的容量 索引处,最后把fwd节点放到旧表的桶上,告知其他线程这个桶已经迁移完成,如果是红黑树,也要先看作链表进行分链,分好后再判断是不是长度大于树化阈值,大于的话还要再次树化,迁移完后又去下一个桶继续迁移,然后线程任务全部处理完,判断是否还有其他线程在扩容,有的话就自己退出,没有了说明自己是最后一个扩容的线程了,就把结束标志位置为true表示扩容完全结束

private final void transfer(ConcurrentHashMap.Node<K, V>[] tab, ConcurrentHashMap.Node<K, V>[] nextTab) {
    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) {
        // initiating,说明是第一个线程进来扩容,还没有其他的
        try {
            //新建一个两倍大小的数组,然后赋值给nexttab,
            // 就是那个保存扩容之后的元素的数组
            @SuppressWarnings("unchecked")
            ConcurrentHashMap.Node<K, V>[] nt = (ConcurrentHashMap.Node<K, V>[]) new ConcurrentHashMap.Node<?, ?>[n << 1];
            nextTab = nt;
        } catch (Throwable ex) {      // try to cope with OOME
            sizeCtl = Integer.MAX_VALUE;
            return;
        }
        //赋值扩容数组
        nextTable = nextTab;
        //记录数组长度
        transferIndex = n;
    }
    int nextn = nextTab.length;
    //新建一个fw节点并把nexttab数组放进去
    ConcurrentHashMap.ForwardingNode<K, V> fwd = new ConcurrentHashMap.ForwardingNode<K, V>(nextTab);
    // advance 参数,该参数指的是是否继续递减转移下一个桶,
    // 如果为 true,表示可以继续向后推进,反之,说明还没有处理好当前桶,不能推进
    boolean advance = true;
    //转移结束标志位
    boolean finishing = false; // to ensure sweep before committing nextTab
    //死循环进行转移
    for(int i = 0, bound = 0; ; ) {
        ConcurrentHashMap.Node<K, V> f;
        int fh;
        //这个循环就是用来领任务的,一开始什么任务都没有,
        // 然后去领任务,领到后就可以退出循环去做任务
        while(advance) {
            //当可以扩容
            //保存下一个索引,以及下一个最小任务区间所在的索引
            int nextIndex, nextBound;
            //i-1大于了bound,表示已经把自己区间里的当前任务都做完了
            if (--i >= bound || finishing)
                advance = false;
            //表示已经没有任务可以分配
            else if ((nextIndex = transferIndex) <= 0) {
                i = -1;
                advance = false;
                //尝试改变nextIndex,改变的方式,当前下一个索引大于最小分配量吗,
                // 要是大于,说明还可以分,那就再分一个区间给她去转移,并把那个值记下来
            } else if (U.compareAndSwapInt(this, TRANSFERINDEX,
                    nextIndex,
                    nextBound = (nextIndex > stride ? nextIndex - stride : 0))) {
                //判断TRANSFERINDEX有没有变化,要是没变,就改成新的转移初始位置
                //如果已经没有最小分配区间了,直接让bound为0,
                // 这样i会一直进第一个if,所以就会把剩下的全部任务都跑完
                bound = nextBound;
                i = nextIndex - 1;
                advance = false;
            }
        }
        //i<0,当前任务做完了
        if (i < 0 || i >= n || i + n >= nextn) {
            int sc;
            if (finishing) {
                nextTable = null;
                table = nextTab;
                sizeCtl = (n << 1) - (n >>> 1);
                return;
            }
            if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) {
                //判断是否还有其他线程在帮助扩容,如果有,那么其他线程会改变sc的值导致这里不相等,
                // 然后就进去,进去后就返回,相当于结束当前线程任务,
                // 但是不结束整个扩容操作,其他的扩容操作还没完成
                if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT)
                    return;
                //如果走到这,说明已经没有其他的线程在帮助扩容了,我已经是最后一个帮助扩容的了
                //然后我的任务也已经全部完成了,所以就把结束标志位置为true,结束整个扩容操作
                finishing = advance = true;
                i = n; // recheck before commit
            }
        } else if ((f = tabAt(tab, i)) == null)
            //如果要转移的桶的位置元素为空,就直接放一个fw节点
            advance = casTabAt(tab, i, null, fwd);
        else if ((fh = f.hash) == MOVED)
            //如果当前节点正在转移,就去领新任务
            advance = true; // already processed
        else {
            //锁住一个桶元素
            synchronized (f) {
                //再次判断当前元素是不是已经移走了
                if (tabAt(tab, i) == f) {
                    ConcurrentHashMap.Node<K, V> ln, hn;
                    if (fh >= 0) {
                        //hash值大于0,是一条链表
                        //与长度相与,由于长度一定是2的n次幂,所以n的二进制表示形式一定是
                        // 100000000这样的,与他相与,所以只会得到0或者1
                        int runBit = fh & n;
                        ConcurrentHashMap.Node<K, V> lastRun = f;
                        //遍历链表
                        //这里做这个操作是为了避免不必要的循环,在这里遍历完后,runbit的值为0或者1
                        //lastrun代表最后一个相与得到rubbit值的节点,它之后的所有节点都与它一样
                        //与n相与都得到runbit
                        /*
                        *     a->b->c->d->f->e
                        * 假设b,c相与会得到1
                        * 1. runbit=0,lastrun=a
                        * 2. 不相同,runbit=1,lastrun=b
                        * 3. 相同,不改变
                        * 4. 不相同,runbit=0,lastrun=d
                        * 5. 从这之后开始都是相同的,所以循环结束runbit=0,lastrun=d
                        * */
                        for(ConcurrentHashMap.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;
                        }
                        //再次遍历这个桶,结束条件是不等于lastrun,
                        // 因为等于lastrun说明后面都是一样的了
                        for(ConcurrentHashMap.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然后将ln前移,相当于头插法
                                //这里采用头插法,因为ln一开始后面就连着那些与他相同的节点
                                //就是与n相与都是runbit的节点,所以用头插法
                                ln = new ConcurrentHashMap.Node<K, V>(ph, pk, pv, ln);
                            else
                                hn = new ConcurrentHashMap.Node<K, V>(ph, pk, pv, hn);
                        }
                        //循环结束,两条链表已经分好了,接着把链放在对应的位置
                        //低链放在原索引处,高链放在原索引加旧表的长度处
                        setTabAt(nextTab, i, ln);
                        setTabAt(nextTab, i + n, hn);
                        //最后将当前这个旧表中的节点设置成fw节点,告诉其他线程,我正在扩容
                        setTabAt(tab, i, fwd);
                        //继续向前移动去获取桶
                        advance = true;
                    } else if (f instanceof ConcurrentHashMap.TreeBin) {
                        //说明这个桶上是一颗红黑树
                        //假设当前节点就是根节点
                        ConcurrentHashMap.TreeBin<K, V> t = (ConcurrentHashMap.TreeBin<K, V>) f;
                        //也要进行分链,分成高链表和低链表
                        ConcurrentHashMap.TreeNode<K, V> lo = null, loTail = null;
                        ConcurrentHashMap.TreeNode<K, V> hi = null, hiTail = null;
                        int lc = 0, hc = 0;
                        //获取这颗树作为链表时的首节点,然后遍历
                        for(ConcurrentHashMap.Node<K, V> e = t.first; e != null; e = e.next) {
                            int h = e.hash;
                            //完全新建一个树节点
                            ConcurrentHashMap.TreeNode<K, V> p = new ConcurrentHashMap.TreeNode<K, V>
                                    (h, e.key, e.val, null, null);
                            if ((h & n) == 0) {
                                //等于0放低链
                                if ((p.prev = loTail) == null)
                                    lo = p;
                                else
                                    loTail.next = p;
                                loTail = p;
                                ++lc;
                            } else {
                                //等于1放高链
                                if ((p.prev = hiTail) == null)
                                    hi = p;
                                else
                                    hiTail.next = p;
                                hiTail = p;
                                ++hc;
                            }
                        }
                        //判断是否大于解树化阈值或者是否已经被分成了两颗树
                        //解树化阈值好理解,就是链表节点少于6个,太少了,所以要把红黑树变成链表
                        //被分成两棵树要重新树化,这是因为之前这个地方就是一颗树,
                        // 现在高低链表都存在,说明有的树节点都被分到另一条链表去了,
                        // 当然现在这个已经不是红黑树了,所以就要把他重新创建成一颗红黑树
                        ln = (lc <= UNTREEIFY_THRESHOLD) ? untreeify(lo) :
                                (hc != 0) ? new ConcurrentHashMap.TreeBin<K, V>(lo) : t;
                        hn = (hc <= UNTREEIFY_THRESHOLD) ? untreeify(hi) :
                                (lc != 0) ? new ConcurrentHashMap.TreeBin<K, V>(hi) : t;
                        //构建完后一样放在对应位置
                        setTabAt(nextTab, i, ln);
                        setTabAt(nextTab, i + n, hn);
                        setTabAt(tab, i, fwd);
                        //然后去下一个桶
                        advance = true;
                    }
                }
            }
        }
    }
}

addCount方法

在添加完元素后,增加数组的大小

主要有两部分,1,把数值加上 2,判断是否需要扩容,要扩容就再进行扩容

private final void addCount(long x, int check) {
    //保存要加的值的value数组
    ConcurrentHashMap.CounterCell[] as;
    long b, s;
    //第一次加一定为空,后面可能不为空,这一部分是把数值加1的逻辑
    if ((as = counterCells) != null ||
            //使用cas操作,判断内存中的basecount与现在的是否相同
            // 如果相同就把base+1,那个x就是1,加完就结束了,这个if都进不去
            !U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) {
        ConcurrentHashMap.CounterCell a;
        long v;
        int m;
        boolean uncontended = true;
        //as是空的,或者当前索引位置的cc对象是空的,
        //没有对象可以加,或者有但是那个值value加失败了,就进行全加
        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) {
        ConcurrentHashMap.Node<K, V>[] tab, nt;
        int n, sc;
        //如果加完后s大于了阈值,就要扩容
        while(s >= (long) (sc = sizeCtl) && (tab = table) != null &&
                (n = tab.length) < MAXIMUM_CAPACITY) {
            //保存扩容标识
            int rs = resizeStamp(n);
            if (sc < 0) {
                //判断扩容操作是不是已经完成,如果完成了就什么也不做,直接退出
                if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
                        sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||
                        transferIndex <= 0)
                    break;
                //没完成就把sc+1,意思是增加一个扩容线程,然后去协助扩容
                if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))
                    transfer(tab, nt);
            } else if (U.compareAndSwapInt(this, SIZECTL, sc,
                    //说明是第一个进来扩容的线程,把sizeCtl变成负数,代表正在扩容
                    (rs << RESIZE_STAMP_SHIFT) + 2))
                transfer(tab, null);
            s = sumCount();
        }
    }
}

sumCount方法

求得数组的大小,也就是concurrenthashmap元素的个数,根据basecount和countercell数组中的值求和得到

final long sumCount() {
    ConcurrentHashMap.CounterCell[] as = counterCells;
    ConcurrentHashMap.CounterCell a;
    long sum = baseCount;
    if (as != null) {
        for(int i = 0; i < as.length; ++i) {
            if ((a = as[i]) != null)
                sum += a.value;
        }
    }
    return sum;
}

fullAddCount方法

完成数组大小的数值加1操作,首先会尝试在baseCount上加1,如果成功就直接退出,否则就尝试去countercell数组找一个位置,然后把那个位置上的value+1,此时如果cc数组没有,还会对数组进行初始化,如果数组有但是当前索引位置没有值,就会创建一个cc对象然后把value赋值返回,如果有cc对象就把对象上的value值加1返回,此时可能一直加失败,就会发生扩容,线程会把数组扩容到cc数组原来的两倍大小,但是扩容的前提是数组长度小于cpu核数,如果大于了就不再扩容,因为扩了也没有意义

// See LongAdder version for explanation
private final void fullAddCount(long x, boolean wasUncontended) {
    int h;
    //求出当前线程的hash值,其实就是要加1,
    // 在CounterCell数组中的哪个位置的元素中的value值加1呢
    //在这里就求出了那个位置的索引,然后准备去到那个位置,
    // 然后把那个位置上的COuntercell对象的value属性+1
        //此方法获取一个随机数,其实就是Random的子类,主要是用来在多线程中获取随机数用的
    if ((h = ThreadLocalRandom.getProbe()) == 0) {
        ThreadLocalRandom.localInit();      // force initialization
        h = ThreadLocalRandom.getProbe();
        wasUncontended = true;
    }
    //是否发生hash冲突
    boolean collide = false;                // 如果最后一个槽非空则为True
    for (;;) {
        //as,保存CounterCell的数组,a,是这个数组中的元素,
        // n,数组长度,v数组的元素的value属性的值
        ConcurrentHashMap.CounterCell[] as; ConcurrentHashMap.CounterCell a; int n; long v;
        if ((as = counterCells) != null && (n = as.length) > 0) {
            //数组长度不为空并且大于0,说明有CounterCell对象可以取,
            // 准备去取一个cc对象然后把他的value属性加1
            if ((a = as[(n - 1) & h]) == null) {
                //进入这里说明按照hash值求的索引,
                // 然后去cc数组找到那个索引位置的cc对象,但是那个cc对象为空
                if (cellsBusy == 0) {
                    //如果数组忙的话,意思是可能其他线程正在使用这个数组,
                    // cellbusy为0代表没有,而为1代表有,
                    // 此时当前线程就不能操作尝试附加新的细胞
                    //在这里就是不忙的情况,可以创建一个cc对象
                    //新建cc对象,并且将x赋值给value属性,但是此时这个cc对象还没放到数组中去
                    ConcurrentHashMap.CounterCell r = new ConcurrentHashMap.CounterCell(x); // Optimistic create
                    if (cellsBusy == 0 &&
                            U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {
                        //这里再次判断是不是忙的,因为可能其他线程又给占用了,
                        // 若不忙就把CELLBUSY置为1,表示正在忙,防止其他线程来干扰
                        //创建标志位为false,表示还没有把cc对象加到数组中去
                        boolean created = false;
                        try {               // Recheck under lock
                            ConcurrentHashMap.CounterCell[] rs; int m, j;
                            if ((rs = counterCells) != null &&
                                    (m = rs.length) > 0 &&
                                    rs[j = (m - 1) & h] == null) {
                                //判断数组不为空,长度大于0,
                                // 且在这个时候那个位置的元素依然是空的,说明可以放进去
                                rs[j] = r;
                                //然后就把这个cc对象放到数组里去,置创建标志位为true,代表真的创建了一个cc对象
                                created = true;
                            }
                        } finally {
                            //最后执行完了,把繁忙的标志位置为0,相当与放开锁,让其他线程可以操作
                            cellsBusy = 0;
                        }
                        //判断是否创建成功,如果成功了,那啥也不用做了,直接退出完成加1操作
                        // 否则的话就自旋重新去找机会插入
                        if (created)
                            break;
                        continue;           // Slot is now non-empty
                    }
                }
                collide = false;
            }
            else if (!wasUncontended)
                // CAS已经知道会失败
                // 但是依旧要重复,就是自旋
                wasUncontended = true;      // 之后继续重复
            else if (U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))
                //这里是那个索引位置的cc对象不为空,那么就尝试去把他的value属性值加1,
                // 通过cas操作,判断与内存中的值是否相同,相同就加一,然后也结束了,就直接返回
                break;

            else if (counterCells != as || n >= NCPU)
                //数组长度大于cpu的核数,则无须扩容,直接重新计算hash然后重新找位置插入
                collide = false;            // At max size or stale
            else if (!collide)
                //置hash冲突为true,表示有hash冲突
                collide = true;
            else if (cellsBusy == 0 &&
                    U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {
                //当不忙的时候,把它置为1,准备对数组扩容
                //这里是一直有hash冲突,且数组长度还没到cpu的核数,
                //所以就想把数组扩容以降低hash冲突
                try {
                    if (counterCells == as) {// Expand table unless stale
                        //扩容的大小为2倍
                     ConcurrentHashMap.CounterCell[] rs = new ConcurrentHashMap.CounterCell[n << 1];
                        for (int i = 0; i < n; ++i)
                            rs[i] = as[i];
                        counterCells = rs;
                    }
                } finally {
                    cellsBusy = 0;
                }
                collide = false;
                continue;                   // Retry with expanded table
            }
            //重新得到一个索引,然后重新去找位置插入,就是自旋操作
            h = ThreadLocalRandom.advanceProbe(h);
        }
        //这里是数组都是空的,还没创建呢,直接给她建一个数组
        else if (cellsBusy == 0 && counterCells == as &&
                U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {
            //一样的,置标志位,然后置初始化为false
            boolean init = false;
            try {                 
                // Initialize table,如果表没有变化的话
                if (counterCells == as) {
                    //默认建2个元素的数组
                    ConcurrentHashMap.CounterCell[] rs = new ConcurrentHashMap.CounterCell[2];
                    //把对应索引位置的cc对象的value属性的值置为1,就是加一操作
                    rs[h & 1] = new ConcurrentHashMap.CounterCell(x);
                    //赋值对象
                    counterCells = rs;
                    //置标志位为true
                    init = true;
                }
            } finally {
                cellsBusy = 0;
            }
            //如果标志位为true,表示已经插入成功了,就直接返回了
            if (init)
                break;
        }
        //最后一种情况,数组还没创建,就尝试去直接把basecount+1,
        // 成功就直接返回了,其他都不用做了
        else if (U.compareAndSwapLong(this, BASECOUNT, v = baseCount, v + x))
            break;                          // Fall back on using base
    }
}

putTreeVal方法

在红黑树上添加一个节点,其他都和hashmap相同,主要是在添加完后的平衡操作,会加一个LockSupport锁,平衡完后又会解锁

final ConcurrentHashMap.TreeNode<K, V> putTreeVal(int h, K k, V v) {
    Class<?> kc = null;
    boolean searched = false;
    for(ConcurrentHashMap.TreeNode<K, V> p = root; ; ) {
        int dir, ph;
        K pk;
        if (p == null) {
            //如果根是空的,说明插入的是根节点,直接创建一个新的
            first = root = new ConcurrentHashMap.TreeNode<K, V>(h, k, v, null, null);
            break;
            //开始查找
        } else if ((ph = p.hash) > h)
            dir = -1;
        else if (ph < h)
            dir = 1;
        else if ((pk = p.key) == k || (pk != null && k.equals(pk)))
            //找到相同的key就返回,直接修改值就行了
            return p;
        else if ((kc == null &&
                (kc = comparableClassFor(k)) == null) ||
                (dir = compareComparables(kc, k, pk)) == 0) {
            //根据搜索标志递归搜索左右子树
            if (!searched) {
                ConcurrentHashMap.TreeNode<K, V> q, ch;
                searched = true;
                if (((ch = p.left) != null &&
                        (q = ch.findTreeNode(h, k, kc)) != null) ||
                        ((ch = p.right) != null &&
                                (q = ch.findTreeNode(h, k, kc)) != null))
                    //找到就返回
                    return q;
            }
            //这都还没找到,说明不存在,就需要新建一个树节点插入了
            dir = tieBreakOrder(k, pk);
        }
        ConcurrentHashMap.TreeNode<K, V> xp = p;
        //判断是插入在叶子节点的左边还是右边
        if ((p = (dir <= 0) ? p.left : p.right) == null) {
            ConcurrentHashMap.TreeNode<K, V> x, f = first;
            first = x = new ConcurrentHashMap.TreeNode<K, V>(h, k, v, f, xp);
            if (f != null)
                f.prev = x;
            if (dir <= 0)
                xp.left = x;
            else
                xp.right = x;
            if (!xp.red)
                x.red = true;
            else {
                //去加写锁,因为要平衡插入了
                lockRoot();
                try {
                    root = balanceInsertion(root, x);
                } finally {
                    //放开锁
                    unlockRoot();
                }
            }
            break;
        }
    }
    assert checkInvariants(root);
    return null;
}

lockRoot方法

加锁的方式是把lockstate置为WRITER态,表示正在写数据,失败的话就去自旋竞争锁

private final void lockRoot() {
    if (!U.compareAndSwapInt(this, LOCKSTATE, 0, WRITER))
        //尝试加锁失败的话,就去竞争锁
        contendedLock(); // offload to separate method
}

unlockRoot方法

解锁,就是把状态置为0

private final void unlockRoot() {
    lockState = 0;
}

contendedLock方法

通过自旋的方式不断获取锁,直到获取到锁然后返回

private final void contendedLock() {
    boolean waiting = false;
    //这是一个死循环,只有在加锁成功的情况下才会返回,否则一直自旋等待锁
    for(int s; ; ) {
        //~WAITER = 11111....01
        //条件成立:说明目前TreeBin中没有读线程在访问 红黑树
        //条件不成立:有线程在访问红黑树
        // 00 01 10
        if (((s = lockState) & ~WAITER) == 0) {
            //是waiter态和0态
            if (U.compareAndSwapInt(this, LOCKSTATE, s, WRITER)) {
                //写线程抢占成功
                if (waiting)
                    waiter = null;
                return;
            }
            lock & 0000...10 = 0, 条件成立:
            // 说明lock 中 waiter 标志位 为0,此时当前线程可以设置为1了,然后将当前线程挂起。
        } else if ((s & WAITER) == 0) {
            if (U.compareAndSwapInt(this, LOCKSTATE, s, s | WAITER)) {
                waiting = true;
                waiter = Thread.currentThread();
            }
            //   //条件成立:说明当前线程在CASE2中已经将
            //   treeBin.waiter 设置为了当前线程,并且将lockState 中表示 等待者标记位的地方 设置为了1
        } else if (waiting)
            //消耗掉一个许可,如果有的话,否则进入等待
            LockSupport.park(this);
    }
}

balanceInsertion方法

与hashmap的平衡插入相同

static <K, V> ConcurrentHashMap.TreeNode<K, V> balanceInsertion(ConcurrentHashMap.TreeNode<K, V> root,
                                                                                     ConcurrentHashMap.TreeNode<K, V> x) {
    x.red = true;
    for(ConcurrentHashMap.TreeNode<K, V> xp, xpp, xppl, xppr; ; ) {
        if ((xp = x.parent) == null) {
            x.red = false;
            return x;
        } else if (!xp.red || (xpp = xp.parent) == null)
            return root;
        if (xp == (xppl = xpp.left)) {
            if ((xppr = xpp.right) != null && xppr.red) {
                xppr.red = false;
                xp.red = false;
                xpp.red = true;
                x = xpp;
            } else {
                if (x == xp.right) {
                    root = rotateLeft(root, x = xp);
                    xpp = (xp = x.parent) == null ? null : xp.parent;
                }
                if (xp != null) {
                    xp.red = false;
                    if (xpp != null) {
                        xpp.red = true;
                        root = rotateRight(root, xpp);
                    }
                }
            }
        } else {
            if (xppl != null && xppl.red) {
                xppl.red = false;
                xp.red = false;
                xpp.red = true;
                x = xpp;
            } else {
                if (x == xp.left) {
                    root = rotateRight(root, x = xp);
                    xpp = (xp = x.parent) == null ? null : xp.parent;
                }
                if (xp != null) {
                    xp.red = false;
                    if (xpp != null) {
                        xpp.red = true;
                        root = rotateLeft(root, xpp);
                    }
                }
            }
        }
    }
}

findTreeNode方法

查找树节点

final ConcurrentHashMap.TreeNode<K, V> findTreeNode(int h, Object k, Class<?> kc) {
    //在当前节点的子树上递归查找,找到就返回
    if (k != null) {
        ConcurrentHashMap.TreeNode<K, V> p = this;
        do {
            int ph, dir;
            K pk;
            ConcurrentHashMap.TreeNode<K, V> q;
            ConcurrentHashMap.TreeNode<K, V> pl = p.left, pr = p.right;
            if ((ph = p.hash) > h)
                p = pl;
            else if (ph < h)
                p = pr;
            else if ((pk = p.key) == k || (pk != null && k.equals(pk)))
                return p;
            else if (pl == null)
                p = pr;
            else if (pr == null)
                p = pl;
            else if ((kc != null ||
                    (kc = comparableClassFor(k)) != null) &&
                    (dir = compareComparables(kc, k, pk)) != 0)
                p = (dir < 0) ? pl : pr;
            else if ((q = pr.findTreeNode(h, k, kc)) != null)
                return q;
            else
                p = pl;
        } while(p != null);
    }
    return null;
}

treeifyBin方法

判断是否达到最小树化容量64,要是没有达到,就只是扩容,不树化,达到了,就将当前桶的链表转换为红黑树,先把所有普通节点改为树节点构建链表,然后树化

private final void treeifyBin(ConcurrentHashMap.Node<K, V>[] tab, int index) {
    ConcurrentHashMap.Node<K, V> b;
    int n, sc;
    if (tab != null) {
        //还没有到最小树化容量就不树化,而是扩容
        if ((n = tab.length) < MIN_TREEIFY_CAPACITY)
            tryPresize(n << 1);
        //树化前判断当前位置不为空且hash>0
        else if ((b = tabAt(tab, index)) != null && b.hash >= 0) {
            //锁住当前桶
            synchronized (b) {
                //值依旧没有改变的话
                if (tabAt(tab, index) == b) {
                    //先链化
                    ConcurrentHashMap.TreeNode<K, V> hd = null, tl = null;
                    for(ConcurrentHashMap.Node<K, V> e = b; e != null; e = e.next) {
                        ConcurrentHashMap.TreeNode<K, V> p =
                                new ConcurrentHashMap.TreeNode<K, V>(e.hash, e.key, e.val,
                                        null, null);
                        if ((p.prev = tl) == null)
                            hd = p;
                        else
                            tl.next = p;
                        tl = p;
                    }
                    //再树化
                    setTabAt(tab, index, new ConcurrentHashMap.TreeBin<K, V>(hd));
                }
            }
        }
    }
}

tryPresize方法

扩容方法,把表的大小扩大2倍

private final void tryPresize(int size) {
    //算出要扩容后的大小
    int c = (size >= (MAXIMUM_CAPACITY >>> 1)) ? MAXIMUM_CAPACITY :
            tableSizeFor(size + (size >>> 1) + 1);
    int sc;
    //大于0,说明没有其他线程在扩容,只有当前线程,
    // 从这里也看出,扩大数组的大小是一个单线程操作
    //而后面的数据转移是多线程操作
    while((sc = sizeCtl) >= 0) {
        ConcurrentHashMap.Node<K, V>[] tab = table;
        int n;
        //表是空的或者大小为0,就是还没有初始化,此时扩容是把一个空表构建出来,但是长度是原来的两倍
        if (tab == null || (n = tab.length) == 0) {
            //新表的长度取较大的那个
            n = (sc > c) ? sc : c;
            //准备扩容,将sc置为-1
            if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
                try {
                    //若表还没有变的话,因为可能其他线程改变了表
                    if (table == tab) {
                        //新建一个表,大小为n
                        @SuppressWarnings("unchecked")
                        ConcurrentHashMap.Node<K, V>[] nt = (ConcurrentHashMap.Node<K, V>[]) new ConcurrentHashMap.Node<?, ?>[n];
                        //重新赋值新表
                        table = nt;
                        //sc=0.75n,其实就是设置sc为下一次扩容阈值
                        sc = n - (n >>> 2);
                    }
                } finally {
                    //重置sizectl的大小
                    sizeCtl = sc;
                }
            }
            //扩容的表大小还没有原来的阈值大,
            // 说明别的线程已经做完这件事了,无须再扩容了,直接返回
        } else if (c <= sc || n >= MAXIMUM_CAPACITY)
            break;
        //说明表不是空的,且此时大小的确不够,还是得扩容
        else if (tab == table) {
            //走到这里,说明扩大数组大小的操作已经完成了,
            // 但是可能还没有完成数据的迁移
            //获取标识符
            int rs = resizeStamp(n);
            //小于0,说明其他线程在扩容,所以协助扩容转移数据
            if (sc < 0) {
                ConcurrentHashMap.Node<K, V>[] nt;
                //再次判断是不是扩容已经结束了,要是结束了就直接返回
                if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
                        sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||
                        transferIndex <= 0)
                    break;
                //没结束,就把sc+1,协助扩容
                if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))
                    transfer(tab, nt);
            } else if (U.compareAndSwapInt(this, SIZECTL, sc,
                    (rs << RESIZE_STAMP_SHIFT) + 2))
                //说明当前线程是第一个进来扩容的,就把sc设置成一个负数,然后开始扩容
                transfer(tab, null);
        }
    }
}

TreeBin方法

这是红黑树的构造方法,传入一个链表头,然后遍历链表构建一颗红黑树

/**
 * 树化
 */
TreeBin(ConcurrentHashMap.TreeNode<K, V> b) {
    //调用父类构造方法,定义hash值为-2
    super(TREEBIN, null, null, null);
    this.first = b;
    ConcurrentHashMap.TreeNode<K, V> r = null;
    //遍历链表
    for(ConcurrentHashMap.TreeNode<K, V> x = b, next; x != null; x = next) {
        next = (ConcurrentHashMap.TreeNode<K, V>) x.next;
        //左右孩子置空
        x.left = x.right = null;
        if (r == null) {
            //说明是第一个节点,置为黑色,重置根节点
            x.parent = null;
            x.red = false;
            r = x;
        } else {
            //不是第一个,要遍历当前树,然后插入
            K k = x.key;
            int h = x.hash;
            Class<?> kc = null;
            for(ConcurrentHashMap.TreeNode<K, V> p = r; ; ) {
                int dir, ph;
                K pk = p.key;
                if ((ph = p.hash) > h)
                    dir = -1;
                else if (ph < h)
                    dir = 1;
                else if ((kc == null &&
                        (kc = comparableClassFor(k)) == null) ||
                        (dir = compareComparables(kc, k, pk)) == 0)
                    dir = tieBreakOrder(k, pk);
                ConcurrentHashMap.TreeNode<K, V> xp = p;
                if ((p = (dir <= 0) ? p.left : p.right) == null) {
                    //左右子树都是空,说明到叶子节点了,然后进行插入
                    x.parent = xp;
                    if (dir <= 0)
                        xp.left = x;
                    else
                        xp.right = x;
                    //平衡插入,这里没有加锁是因为在这之前已经用
                    // synchorinzed关键字锁住了整个桶
                    r = balanceInsertion(r, x);
                    break;
                }
            }
        }
    }
    //树化后重新赋值根节点
    this.root = r;
    assert checkInvariants(root);
}

setTabAt方法

设置指定桶位置的元素值

static final <K, V> void setTabAt(ConcurrentHashMap.Node<K, V>[] tab, int i, ConcurrentHashMap.Node<K, V> v) {
    U.putObjectVolatile(tab, ((long) i << ASHIFT) + ABASE, v);
}

扩容

concurrenthashmap remove 会销毁对象吗_初始化

进sc<0说明有别人在扩容,需要去帮助扩容,所以先判断扩容操作是不是已经完成了,要是完成了就啥也不做直接退出,要是没完成,就先把sc加1,代表扩容线程又多了一个,然后去协助扩容

进else说明是第一个进来扩容的线程,所以是直接把sc设置成一个很小的负数类似 -0x7fffff,然后进行扩容

concurrenthashmap remove 会销毁对象吗_java_02

这一步是在自己的扩容任务已经做完了,且没有可以分配的任务了,然后就把sc-1,其实就是减去一个扩容线程,跟上面的sc+1对应,然后判断这个值有没有变:

(sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT)
这个式子把2移到右边去不就是下面这个式子吗,也就是开始扩容时设置的负数
sc=(rs << RESIZE_STAMP_SHIFT) + 2)

所以如果相等,说明已经没有线程在扩容了,就结束

如果不相等,说明还有线程在那之后也来协助扩容,且将sc+1了,所以才不相等,所以就不能结束,直接return,相当于结束当前线程的扩容任务,但是并没有结束其他线程的任务,只有在这个值相等的情况下,说明已经没有其他线程来协助扩容了,当前线程是最后一个线程,所以把finishing置为true,这样在上面就可以退出循环,真正的完全结束扩容操作。

删除操作

remove方法

public boolean remove(Object key, Object value) {
    if (key == null)
        throw new NullPointerException();
    return value != null && replaceNode(key, null, value) != null;
}

replaceNode方法

移除和替换都用这个方法,通过value是否为空来判断是替换还是删除,因为ConcurrentHashMap没有null值

final V replaceNode(Object key, V value, Object cv) {
    //求得正的hash值
    int hash = spread(key.hashCode());
    for(ConcurrentHashMap.Node<K, V>[] tab = table; ; ) {
        //遍历表
        ConcurrentHashMap.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) {
                    if (fh >= 0) {
                        //是一条链表
                        validated = true;
                        for(ConcurrentHashMap.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;
                                //hash相同key相同,找到了
                                //cv为空或者找到的值相等或者内容相等
                                if (cv == null || cv == ev ||
                                        (ev != null && cv.equals(ev))) {
                                    //保存老的值
                                    oldVal = ev;
                                    //判断是要删除吗,value不为空表示不是删除,
                                    // 是修改,所以把老的值覆盖
                                    if (value != null)
                                        e.val = value;
                                    //否则就是删除,移除链表节点,这里是前驱不为空
                                    else if (pred != null)
                                        pred.next = e.next;
                                    else
                                        //说明前驱是空的,删除的是头节点,
                                        // 所以把桶位置的头节点替换为e.next
                                        setTabAt(tab, i, e.next);
                                }
                                break;
                            }
                            pred = e;
                            //最后找完了也没找到,就退出
                            if ((e = e.next) == null)
                                break;
                        }
                    } else if (f instanceof ConcurrentHashMap.TreeBin) {
                        //说明是树节点
                        validated = true;
                        ConcurrentHashMap.TreeBin<K, V> t = (ConcurrentHashMap.TreeBin<K, V>) f;
                        ConcurrentHashMap.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;
}

removeTreeNode方法

移除树节点,和hashmap一样,不过要加锁

final boolean removeTreeNode(ConcurrentHashMap.TreeNode<K, V> p) {
    ConcurrentHashMap.TreeNode<K, V> next = (ConcurrentHashMap.TreeNode<K, V>) p.next;
    ConcurrentHashMap.TreeNode<K, V> pred = p.prev;  // unlink traversal pointers
    ConcurrentHashMap.TreeNode<K, V> r, rl;
    //作为链表的删除操作start---------
    if (pred == null)
        first = next;
    else
        pred.next = next;
    if (next != null)
        next.prev = pred;
    if (first == null) {
        //头节点是空的说明只有一个节点,把根节点置空直接返回true,表示要树化
        root = null;
        return true;
    }
    //作为链表删除操作end---------------
    //这里是判断删除后树的节点个数太少了,最多6个
    if ((r = root) == null || r.right == null || // too small
            (rl = r.left) == null || rl.left == null)
        //也返回true,需要解树化
        return true;
    //***************开始作为红黑树的删除start*******************
    //上锁
    lockRoot();
    try {
        //替换节点
        ConcurrentHashMap.TreeNode<K, V> replacement;
        ConcurrentHashMap.TreeNode<K, V> pl = p.left;
        ConcurrentHashMap.TreeNode<K, V> pr = p.right;
        //********左右子树不为空start*******
        if (pl != null && pr != null) {
            ConcurrentHashMap.TreeNode<K, V> s = pr, sl;
            //查找后继节点
            while((sl = s.left) != null) // find successor
                s = sl;
            //替换后继节点和当前节点的颜色
            boolean c = s.red;
            s.red = p.red;
            p.red = c; // swap colors
            ConcurrentHashMap.TreeNode<K, V> sr = s.right;
            ConcurrentHashMap.TreeNode<K, V> pp = p.parent;
            //如果后继就是当前节点的右孩子,替换两个节点
            if (s == pr) { // p was s's direct parent
                p.parent = s;
                s.right = p;
            } else {
                //后继是其他节点的孩子
                ConcurrentHashMap.TreeNode<K, V> sp = s.parent;
                //把p移到后继的父亲下面
                if ((p.parent = sp) != null) {
                    if (s == sp.left)
                        sp.left = p;
                    else
                        sp.right = p;
                }
                //把p的右孩子设置为s的右孩子
                if ((s.right = pr) != null)
                    pr.parent = s;
            }
            //左孩子置空
            p.left = null;
            //把s的右孩子设置为p的右孩子
            if ((p.right = sr) != null)
                sr.parent = p;
            //把p的左孩子设置成s的左孩子
            if ((s.left = pl) != null)
                pl.parent = s;
            //s的父亲设置成p的父亲,若是空说明是根节点,重新设置根节点
            if ((s.parent = pp) == null)
                r = s;
            //判断是pp的左孩子还是右孩子
            else if (p == pp.left)
                pp.left = s;
            else
                pp.right = s;
            //找出替换节点
            if (sr != null)
                replacement = sr;
            else
                replacement = p;
        }//*****左右子树不为空end**********
        else if (pl != null)
            //左子树不为空右子树空,替换节点为左孩子
            replacement = pl;
        else if (pr != null)
            //反过来,为右孩子
            replacement = pr;
        else
            //没有替换,删除自己
            replacement = p;
        //两个不相等,可以先删除p,让rep顶上
        if (replacement != p) {
            ConcurrentHashMap.TreeNode<K, V> pp = replacement.parent = p.parent;
            if (pp == null)
                r = replacement;
            else if (p == pp.left)
                pp.left = replacement;
            else
                pp.right = replacement;
            p.left = p.right = p.parent = null;
        }
        //之后平衡删除,这里只有黑色删除才进平衡删除,
        // 红色不进,因为删除红色无影响
        root = (p.red) ? r : balanceDeletion(r, replacement);
        //相等就先平衡删除,然后删除p
        if (p == replacement) {  // detach pointers
            ConcurrentHashMap.TreeNode<K, V> pp;
            if ((pp = p.parent) != null) {
                if (p == pp.left)
                    pp.left = null;
                else if (p == pp.right)
                    pp.right = null;
                p.parent = null;
            }
        }
    } finally {
        //放开锁
        unlockRoot();
    }
    assert checkInvariants(root);
    return false;
}

balanceDeletion方法

平衡删除,和hashmap一样

static <K, V> ConcurrentHashMap.TreeNode<K, V> balanceDeletion(ConcurrentHashMap.TreeNode<K, V> root,
                                                                                    ConcurrentHashMap.TreeNode<K, V> x) {
    for(ConcurrentHashMap.TreeNode<K, V> xp, xpl, xpr; ; ) {
        if (x == null || x == root)
            return root;
        else if ((xp = x.parent) == null) {
            x.red = false;
            return x;
        } else if (x.red) {
            x.red = false;
            return root;
        } else if ((xpl = xp.left) == x) {
            //********x是父亲的左孩子start**********
            if ((xpr = xp.right) != null && xpr.red) {
                //兄弟是红色,不是真兄弟,要左旋找兄弟
                xpr.red = false;
                xp.red = true;
                root = rotateLeft(root, xp);
                xpr = (xp = x.parent) == null ? null : xp.right;
            }
            //兄弟为空,自损到上一层
            if (xpr == null)
                x = xp;
            else {
                ConcurrentHashMap.TreeNode<K, V> sl = xpr.left, sr = xpr.right;
                //兄弟的左右孩子为空,也要自损,但是同时把兄弟置为红色
                if ((sr == null || !sr.red) &&
                        (sl == null || !sl.red)) {
                    xpr.red = true;
                    x = xp;
                } else {
                    //有一个孩子为红色,可以借
                    if (sr == null || !sr.red) {
                        //借的是左孩子,需要先右旋
                        if (sl != null)
                            sl.red = false;
                        xpr.red = true;
                        root = rotateRight(root, xpr);
                        xpr = (xp = x.parent) == null ?
                                null : xp.right;
                    }
                    //右旋完了,再随着父亲左旋
                    if (xpr != null) {
                        xpr.red = (xp == null) ? false : xp.red;
                        if ((sr = xpr.right) != null)
                            sr.red = false;
                    }
                    if (xp != null) {
                        xp.red = false;
                        root = rotateLeft(root, xp);
                    }
                    x = root;
                }
            }
            //结束end
        } else { // symmetric 对称操作
            if (xpl != null && xpl.red) {
                xpl.red = false;
                xp.red = true;
                root = rotateRight(root, xp);
                xpl = (xp = x.parent) == null ? null : xp.left;
            }
            if (xpl == null)
                x = xp;
            else {
                ConcurrentHashMap.TreeNode<K, V> sl = xpl.left, sr = xpl.right;
                if ((sl == null || !sl.red) &&
                        (sr == null || !sr.red)) {
                    xpl.red = true;
                    x = xp;
                } else {
                    if (sl == null || !sl.red) {
                        if (sr != null)
                            sr.red = false;
                        xpl.red = true;
                        root = rotateLeft(root, xpl);
                        xpl = (xp = x.parent) == null ?
                                null : xp.left;
                    }
                    if (xpl != null) {
                        xpl.red = (xp == null) ? false : xp.red;
                        if ((sl = xpl.left) != null)
                            sl.red = false;
                    }
                    if (xp != null) {
                        xp.red = false;
                        root = rotateRight(root, xp);
                    }
                    x = root;
                }
            }
        }
    }
}

untreeify方法

解树化,把树变成链表

static <K, V> ConcurrentHashMap.Node<K, V> untreeify(ConcurrentHashMap.Node<K, V> b) {
    ConcurrentHashMap.Node<K, V> hd = null, tl = null;
    //很简单,就是新建一个相同的节点,然后构建一条新的链表返回
    for(ConcurrentHashMap.Node<K, V> q = b; q != null; q = q.next) {
        ConcurrentHashMap.Node<K, V> p = new ConcurrentHashMap.Node<K, V>(q.hash, q.key, q.val, null);
        if (tl == null)
            hd = p;
        else
            tl.next = p;
        tl = p;
    }
    return hd;
}

get操作

get方法

public V get(Object key) {
    ConcurrentHashMap.Node<K, V>[] tab;
    ConcurrentHashMap.Node<K, V> e, p;
    int n, eh;
    K ek;
    //获取一个一定是正的hash值
    //获取key的hash值
    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;
        } 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;
}

find方法

ConcurrentHashMap中的find方法进行了多次重写,然后在运行时才会确定是走哪一个find方法

最普通的节点方法,直接按照链表来查找值

ConcurrentHashMap.Node<K, V> find(int h, Object k) {
    ConcurrentHashMap.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;
}

forwordingnode的find方法

ConcurrentHashMap.Node<K, V> find(int h, Object k) {
    // loop to avoid arbitrarily deep recursion on forwarding nodes
    outer:
    for(ConcurrentHashMap.Node<K, V>[] tab = nextTable; ; ) {
        ConcurrentHashMap.Node<K, V> e;
        int n;
        if (k == null || tab == null || (n = tab.length) == 0 ||
                (e = tabAt(tab, (n - 1) & h)) == null)
            return null;
        for(; ; ) {
            int eh;
            K ek;
            if ((eh = e.hash) == h &&
                    ((ek = e.key) == k || (ek != null && k.equals(ek))))
                return e;
            if (eh < 0) {
                //如果是fw节点就继续查,否则调用这个节点自己的查找方法
                if (e instanceof ConcurrentHashMap.ForwardingNode) {
                    tab = ((ConcurrentHashMap.ForwardingNode<K, V>) e).nextTable;
                    continue outer;
                } else
                    return e.find(h, k);
            }
            if ((e = e.next) == null)
                return null;
        }
    }
}

ReservationNode的find

ConcurrentHashMap.Node<K, V> find(int h, Object k) {
    return null;
}

treenode的find

ConcurrentHashMap.Node<K, V> find(int h, Object k) {
    return findTreeNode(h, k, null);
}

treebin的find

final ConcurrentHashMap.Node<K, V> find(int h, Object k) {
    if (k != null) {
        for(ConcurrentHashMap.Node<K, V> e = first; e != null; ) {
            int s;
            K ek;
            //判断当前锁状态是不是可以获取
            if (((s = lockState) & (WAITER | WRITER)) != 0) {
                if (e.hash == h &&
                        ((ek = e.key) == k || (ek != null && k.equals(ek))))
                    //能获取就获取判断,值相同就返回,否则进入下一次循环
                    return e;
                e = e.next;
                //不能获取就尝试去获取锁
            } else if (U.compareAndSwapInt(this, LOCKSTATE, s,
                    s + READER)) {
                ConcurrentHashMap.TreeNode<K, V> r, p;
                try {
                    //获取成功了,就开始查找
                    p = ((r = root) == null ? null :
                            r.findTreeNode(h, k, null));
                } finally {
                    //查找完了,放开后面来的等待的线程,让他去抢到锁
                    Thread w;
                    if (U.getAndAddInt(this, LOCKSTATE, -READER) ==
                            (READER | WAITER) && (w = waiter) != null)
                        LockSupport.unpark(w);
                }
                return p;
            }
        }
    }
    return null;
}