史上最详细的ConcurrentHashMap源码解析

1. put 方法

 内部调用 putVal方法

putVal 方法

final V putVal(K key, V value, boolean onlyIfAbsent) {
        //校验key和value 非空验证,如果为空 报NullPointerException异常
        if (key == null || value == null) throw new NullPointerException();
        //通过spread 函数,生辰hash值
        int hash = spread(key.hashCode());
        int binCount = 0;
        //死循环,给数组赋值,table 为局部全局变量
        for (ConcurrentHashMap.Node<K, V>[] tab = table; ; ) {
            /**
             * 声明变量
             * f:当前数组中头节点
             * n:数组长度
             * i:(n - 1) & hash) 计算出来的位置索引
             * fh:当前头节点的hash值
             */
            ConcurrentHashMap.Node<K, V> f;
            int n, i, fh;
            //非空验证,防止 table  为null
            if (tab == null || (n = tab.length) == 0)
                //如果tab为空,初始化
                tab = initTable();
                //根据(n - 1) & hash) 计算出 i,然后获取 tab中i位置上的val,赋值给f
                //判断f 是否==null
            else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
                //cas 方式 给i位置上 设置 值,直到设置成功
                if (casTabAt(tab, i, null,
                        new ConcurrentHashMap.Node<K, V>(hash, key, value, null)))
                    break;                   // no lock when adding to empty bin
            }
            //判断f节点的hash值是否==MOVED(-1)
            //MOVED 表示当前位置正在数据迁移中
            else if ((fh = f.hash) == MOVED)
                //让当前插入的线程去协助扩容
                tab = helpTransfer(tab, f);
            else {
                //数组 i位置上已经有值
                V oldVal = null;
                //上锁,锁住头节点
                synchronized (f) {
                    //二次验证,从数组中获取的值==f
                    if (tabAt(tab, i) == f) {
                        if (fh >= 0) {//判断fh为非负值
                            binCount = 1;
                            //循环并递增binCount,记录链表长度
                            for (ConcurrentHashMap.Node<K, V> e = f; ; ++binCount) {
                                K ek;
                                //如果当前节点的hash==hash && key也相等
                                if (e.hash == hash &&
                                        ((ek = e.key) == key ||
                                                (ek != null && key.equals(ek)))) {
                                    //记录val
                                    oldVal = e.val;
                                    // 如果传入的是false,代表key一致,覆盖value
                                    // 如果传入的是true,代表key一致,什么都不做!
                                    if (!onlyIfAbsent)
                                        e.val = value;
                                    break;
                                }
                                //声明pred 指针,指向 当前节点 e
                                ConcurrentHashMap.Node<K, V> pred = e;
                                //如果e节点的next==null,创建新的节点并挂在链表的最后面
                                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) {
                    //TREEIFY_THRESHOLD=8, binCount是否大于8(链表长度是否 >= 8)
                    if (binCount >= TREEIFY_THRESHOLD)
                        //尝试转为红黑树或者扩容
                        // 当数组长度大于等于64 and 链表长度大于等于8 的时候,转换红黑树
                        treeifyBin(tab, i);
                    if (oldVal != null)
                        return oldVal;
                    break;
                }
            }
        }
        addCount(1L, binCount);
        return null;
    }

hashcode状态

// static final int MOVED     = -1; // 代表当前hash位置的数据正在扩容!
// static final int TREEBIN   = -2; // 代表当前hash位置下挂载的是一个红黑树
// static final int RESERVED  = -3; // 预留当前索引位置……

为什么链表长度为8转换红黑树? 

     泊松分布 

The main disadvantage of per-bin locks is that other update
 * operations on other nodes in a bin list protected by the same
 * lock can stall, for example when user equals() or mapping
 * functions take a long time.  However, statistically, under
 * random hash codes, this is not a common problem.  Ideally, the
 * frequency of nodes in bins follows a Poisson distribution
 * (http://en.wikipedia.org/wiki/Poisson_distribution) with a
 * parameter of about 0.5 on average, given the resizing threshold
 * of 0.75, although with a large variance because of resizing
 * granularity. Ignoring variance, the expected occurrences of
 * list size k are (exp(-0.5) * pow(0.5, k) / factorial(k)). The
 * first values are:
 *
 * 0:    0.60653066
 * 1:    0.30326533
 * 2:    0.07581633
 * 3:    0.01263606
 * 4:    0.00157952
 * 5:    0.00015795
 * 6:    0.00001316
 * 7:    0.00000094
 * 8:    0.00000006
 * more: less than 1 in ten million

spread方法

/**
     * // 传入hashcode   h,
     * // 1.将h的高16位 带符号位右移动16位,高16变低16
     * // 2.h的低16位 异或  h的高16位,
     * // 3.最后再 & 上 HASH_BITS ,HASH_BITS=01111111 11111111 11111111 11111111
     * // HASH_BITS让hash值的最高位符号位肯定为0,代表当前hash值默认情况下一定是正数,因为hash值为负数时,有特殊的含义
     */
    static final int spread(int h) {
        /**
         * 举例
         *  h= 00001101 00001101 00101111 10001111
         *  1. 高16位 右移动16位 后 = 00000000 00000000 00001101 00001101
         *  2.
         *    00001101 00001101 00101111 10001111
         *  ^
         *    00000000 00000000 00001101 00001101
         *  = 00001101 00001101 00100010 10000010
         *
         *  3. 再 & 上HASH_BITS
         *  00001101 00001101 00100010 10000010
         *  &
         *  01111111 11111111 11111111 11111111
         *  =00001101 00001101 00100010 10000010
         *
         */
        return (h ^ (h >>> 16)) & HASH_BITS;
    }

initTable方法

/**
     * sizeCtl:是数组在初始化和扩容操作时的一个控制变量
     * -1:代表当前数组正在初始化
     * 小于-1:低16位代表当前数组正在扩容的线程个数(如果1个线程扩容,值为-2,如果2个线程扩容,值为-3)
     * 0:代表数组还没初始化
     * 大于0:代表当前数组的扩容阈值,或者是当前数组的初始化大小
     *
     */
	// 初始化数组方法
    private final Node<K,V>[] initTable() {
	    //声明变量 
        Node<K,V>[] tab; int sc;
		//将全局变量table 赋值给tab ,
		// 如果tab==null || tab.length==0 ,循环成立,继续初始化数组
        while ((tab = table) == null || tab.length == 0) {
		    //如果sizeCtl<0,说明当前已经有线程正在初始化中
            if ((sc = sizeCtl) < 0)
                Thread.yield(); //让出线程,当前线程进入就绪状态
			// 可以尝试初始化数组,线程会以CAS的方式,将sizeCtl修改为-1,代表当前线程可以初始化数组
            else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
                try {
					// 再次判断当前数组是否已经初始化完毕。
                    if ((tab = table) == null || tab.length == 0) {
						// 开始初始化,
						// 如果sizeCtl > 0,就初始化sizeCtl长度的数组
						// 如果sizeCtl == 0,就初始化默认的长度16    
                        int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
                        @SuppressWarnings("unchecked")
                        Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
						 // 将初始化的数组nt,赋值给tab和table
                        table = tab = nt;
						// sc赋值为了数组长度 - 数组长度 右移 2位    16 - 4 = 12
						// 将sc赋值为下次扩容的阈值
                        sc = n - (n >>> 2);
                    }
                } finally {
					// 将赋值好的sc,设置给sizeCtl
                    sizeCtl = sc;
                }
                break;
            }
        }
        return tab;
    }

2. treeifyBin 方法

   /**
     *  tab 原数组
     *  index 要放入的位置
     */
    private final void treeifyBin(Node<K,V>[] tab, int index) {
        //b:index位置上的头节点
        //n:tab数组的长度
        //sc:
        Node<K,V> b; int n, sc;
        if (tab != null) {
            //判断数组长度是否小于64
            if ((n = tab.length) < MIN_TREEIFY_CAPACITY)
                //执行扩容数组操作,n 左移动1位
                tryPresize(n << 1);
            //获取index位置上的头节点并赋值给b,
            //头节点的hash是非负数
            else if ((b = tabAt(tab, index)) != null && b.hash >= 0) {
                //加锁
                synchronized (b) {
                    //二次验证,防止并发  dcl
                    if (tabAt(tab, index) == b) {
                        TreeNode<K,V> hd = null, tl = null;
                        for (Node<K,V> e = b; e != null; e = e.next) {
                            TreeNode<K,V> p =
                                    new 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 TreeBin<K,V>(hd));
                    }
                }
            }
        }
    }

3. tryPresize 扩容方法

 /**
     *
     * @param size  新数组长度
     */
    private final void tryPresize(int size) {
        //如果size超出了最大值,就是最大值,
        //否则:需要保证数组长度是2的n次幂
        //注意:这块的操作,是为了初始化操作准备的,因为调用putAll方法时,也会触发tryPresize方法
        //如果刚刚new的ConcurrentHashMap直接调用了putAll方法的话,会通过tryPresize方法进行初始化
        int c = (size >= (MAXIMUM_CAPACITY >>> 1)) ? MAXIMUM_CAPACITY :
                tableSizeFor(size + (size >>> 1) + 1);
        //sc:sizeCtl赋值给 sc
        int sc;
        // sizeCtl 在这代表着 数组的长度,并判断是否大于0,这里代表没有初始化操作,也没有扩容操作
        while ((sc = sizeCtl) >= 0) {
            //tab:全局变量的table
            //n:tab 的数组长度
            ConcurrentHashMap.Node<K,V>[] tab = table; int n;
            //非空验证,长度验证
            if (tab == null || (n = tab.length) == 0) {
                //重定向 最大的长度 操作
                // sc是初始化长度,初始化长度如果比计算出来的c要大的话,直接使用sc,如果没有sc大,
                // 说明sc无法容纳下putAll中传入的map,使用更大的数组长度
                n = (sc > c) ? sc : c;
                // cas方式设置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;
                    }
                }
            }
            // 如果计算出来的长度c如果小于等于sc,直接退出循环结束方法
            // 数组长度大于等于最大长度了,直接退出循环结束方法
            else if (c <= sc || n >= MAXIMUM_CAPACITY)
                break;
            else if (tab == table) {//二次判断,
                int rs = resizeStamp(n);
                // 说明有线程正在扩容,过来帮助扩容
                if (sc < 0) {
                    ConcurrentHashMap.Node<K,V>[] nt;
                    if (
                        // 依然有BUG
                        // 当前线程扩容时,老数组长度是否和我当前线程扩容时的老数组长度一致
                        // 00000000 00000000 10000000 00011010
                            (sc >>> RESIZE_STAMP_SHIFT) != rs
                                    // 10000000 00011010 00000000 00000010
                                    // 00000000 00000000 10000000 00011010
                                    // 这两个判断都是有问题的,核心问题就应该先将rs左移16位,再追加当前值。
                                    // 这两个判断是BUG
                                    // 判断当前扩容是否已经即将结束
                                    || sc == rs + 1
                                    // 判断当前扩容的线程是否达到了最大限度     // sc == rs << 16 + MAX_RESIZERS BUG
                                    ||  sc == rs + MAX_RESIZERS
                                    // 扩容已经结束了。
                                    || (nt = nextTable) == null
                                    // 记录迁移的索引位置,从高位往低位迁移,也代表扩容即将结束。
                                    ||  transferIndex <= 0
                      )
                        break;
                    // 如果线程需要协助扩容,首先就是对sizeCtl进行+1操作,代表当前要进来一个线程协助扩容
                    if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))
                        // 上面的判断没进去的话,nt就代表新数组
                        transfer(tab, nt);
                }
                // 是第一个来扩容的线程
                // 基于CAS将sizeCtl修改为  10000000 00011010 00000000 00000010
                // 将扩容戳左移16位之后,符号位是1,就代码这个值为负数
                // 低16位在表示当前正在扩容的线程有多少个,
                // 为什么低位值为2时,代表有一个线程正在扩容
                // 每一个线程扩容完毕后,会对低16位进行-1操作,当最后一个线程扩容完毕后,减1的结果还是-1,
                // 当值为-1时,要对老数组进行一波扫描,查看是否有遗漏的数据没有迁移到新数组
                else if (U.compareAndSwapInt(this, SIZECTL, sc,
                        (rs << RESIZE_STAMP_SHIFT) + 2))
                    // 调用transfer方法,并且将第二个参数设置为null,就代表是第一次来扩容!
                    transfer(tab, null);
            }
        }
    }

4. transfer 方法

    /**
     * tab:olg 数组
     * nextTab: 新的数组
     */
    private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {
        //记录老数组的长度
        //stride : 计算出来的每次迁移的数据长度
        int n = tab.length, stride;
        // 基于cpu的内核数量来计算,每个线程一次迁移多少长度的数据
        // 举例说明:ncpu=4  数组长度=1024
        // 计算: 1024 - 512 - 256 - 128 / 4 = 32
        // MIN_TRANSFER_STRIDE 默认最小长度是 16
        if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)
            stride = MIN_TRANSFER_STRIDE; // 如果计算出来的长度小于MIN_TRANSFER_STRIDE长度,默认就用MIN_TRANSFER_STRIDE 长度
        if (nextTab == null) { //判断新的tab是否为空          
            try {
                @SuppressWarnings("unchecked")
                //如果为null,创建一个新的 数组 nt
                Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n << 1];
                //赋值给 nextTab
                nextTab = nt;
            } catch (Throwable ex) {      // try to cope with OOME
                sizeCtl = Integer.MAX_VALUE;
                return;
            }
            //全局的 nextTable
            nextTable = nextTab;
             // 迁移数据时,用到的标识,默认值为老数组长度
            transferIndex = n;
        }
        //新数组的长度
        int nextn = nextTab.length;
        //创建一个ForwardingNode对象,作为老数组该位置上的值迁移后的标记,fwd的hashcode=-1
        ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab);
        //迁移标记
        boolean advance = true;
        //代表是否迁移完成
        boolean finishing = false; // to ensure sweep before committing nextTab
        for (int i = 0, bound = 0;;) {
            //f:
            //fh:
            Node<K,V> f; int fh;
            /**** 领取任务 ******/
            while (advance) {
                //nextIndex:
                //nextBound:
                int nextIndex, nextBound;
                //如果第一次进来 i=0,bound=0 ,--i>=bound 肯定不成立
                //对i进行--,并且判断当前任务是否处理完成
                if (--i >= bound || finishing)
                    advance = false;

                //给nextIndex赋值=transferIndex
                //判断 transferIndex是否小于等于0,代表没有任务领取了,  结束了
                else if ((nextIndex = transferIndex) <= 0) {
                    i = -1;
                    advance = false;
                }
                //cas 操作,用来设置TRANSFERINDEX 的值, nextIndex - stride 
                // 在任务都领取完之后,transferIndex肯定是小于等于0的,代表没有迁移数据的任务可以领取
                else if (U.compareAndSwapInt
                         (this, TRANSFERINDEX, nextIndex,
                          nextBound = (nextIndex > stride ?
                                       nextIndex - stride : 0))) {
                      // 对bound赋值                   
                    bound = nextBound;
                    / 对i赋值
                    i = nextIndex - 1;
                    // 设置advance设置为false,代表当前线程领取到任务了。
                    advance = false;
                }
            }
            /**** 迁移结束操作 ******/
            // i < 0:当前线程没有接收到任务!
            // i >= n: 迁移的索引位置,不可能大于数组的长度,不会成立
            // i + n >= nextn:因为i最大值就是数组索引的最大值,不会成立
            if (i < 0 || i >= n || i + n >= nextn) {
            // 如果进来,代表当前线程没有接收到任务
                int sc;
                //finishing=true  代表着迁移完成了
                if (finishing) {
                    // 将nextTable新数组设置为null
                    nextTable = null;
                    // 将当前数组的引用指向了新数组~
                    table = nextTab;
                    // 重新计算扩容阈值    64 - 16 = 48
                    sizeCtl = (n << 1) - (n >>> 1);
                      // 结束扩容
                    return;
                }
                // 当前线程没有接收到任务,让当前线程结束扩容操作。
                // 采用CAS的方式,将sizeCtl - 1,代表当前并发扩容的线程数 - 1
                if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) {
                    // sizeCtl的高16位是基于数组长度计算的扩容戳,低16位是当前正在扩容的线程个数
                    if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT)
                        // 代表当前线程并不是最后一个退出扩容的线程,直接结束当前线程扩容
                        return;
                    // 如果是最后一个退出扩容的线程,将finishing和advance设置为true
                    finishing = advance = true;
                    // 将i设置为老数组长度,让最后一个线程再从尾到头再次检查一下,是否数据全部迁移完毕。
                    i = n; // recheck before commit
                }
            }
             // 开始迁移数据,并且在迁移完毕后,会将advance设置为true 
            // 获取指定i位置的Node对象,并且判断是否为null
            else if ((f = tabAt(tab, i)) == null)
                // 当前桶位置没有数据,无需迁移,直接将当前桶位置设置为fwd
                advance = casTabAt(tab, i, null, fwd);
            // 拿到当前i位置的hash值,如果为MOVED,证明数据已经迁移过了。
            else if ((fh = f.hash) == MOVED)
                // 一般是给最后扫描时,使用的判断,如果迁移完毕,直接跳过当前位置。
                advance = true; // already processed
            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;
                        }
                    }
                }
            }
        }
    }

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值