ConcurrentHashMap 1.8源码详解

一、 jdk1.8容器初始化

1.1 源码分析

//没有进行任何操作,在添加数据时才会创建数组
 public ConcurrentHashMap() {
    }
//initialCapacity:指定数组初始长度(创建出的数组长度不是这个值)
public ConcurrentHashMap(int initialCapacity) {
        if (initialCapacity < 0)
            throw new IllegalArgumentException();
        //MAXIMUM_CAPACITY = 1 << 30,正常情况下,数组长度不会指定这么大,因此程序进入tableSizeFor()
        int cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ?
                   MAXIMUM_CAPACITY :
                   //方法内的参数表示变为初始容量的1.5倍+1
                   //举例:当初始容量为16时,最终参数的值为25
                   tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1));
        //cap表示真正的数组初始长度,将其赋值给sizeCtl
        //sizeCtl的含义下文中会介绍
        //此时,只是计算出数组的初始长度,并没有创建出数组
        this.sizeCtl = cap;
    }

//将传进来的参数变为2的次幂
//具体实现是通过位或运算使其所有位都变为1(举例:11000 -> 11111)
//举例:当c=25时
private static final int tableSizeFor(int c) {
		//n=24
        int n = c - 1;
        //n=24二进制表示为11000,n右移一位为01100,位或操作后n=28(11100)
        n |= n >>> 1;
        //28二进制表示为11100,n右移2位为00111,位或操作后n=31(11111)
        n |= n >>> 2;
        //31二进制表示为11111,n右移4位为00001,位或操作后n=31(11111)
        n |= n >>> 4;
        //下边的指令执行完后,n还为31
        n |= n >>> 8;
        n |= n >>> 16;
        //n+1==32
        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
    }

数组长度为什么是2的n次幂,可以查看我的上一篇文章: ConcurrentHashMap的容量为什么是2的n次幂.

//传入的参数为一个map集合,基于这个集合创建一个concurrentHashMap
public ConcurrentHashMap(Map<? extends K, ? extends V> m) {
		//将sizeCtl赋值为默认初始容量16
        this.sizeCtl = DEFAULT_CAPACITY;
        putAll(m);
    }

1.2 sizeCtl含义解释

sizeCtl = 0,数组未初始化,数组默认长度为16
sizeCtl > 0,数组初始化前,表示数组长度,数组初始化后,表示扩容的阈值
sizeCtl = -1,数组正在初始化或扩容
sizeCtl < 0 且 不等于-1,sizeCtl的高16位表示扩容标识戳,低16位表示帮助扩容的线程数量+1

二、jdk1.8添加安全

2.1 源码分析

2.1.1 添加元素put/putVal方法

 public V put(K key, V value) {
        return putVal(key, value, false);
    }
final V putVal(K key, V value, boolean onlyIfAbsent) {
		//不允许空键空值
        if (key == null || value == null) throw new NullPointerException();
        //根据key的hashCode方法得到hash值,再通过扰动算法使散列表分布均匀,得到最终的hash值
        //计算出的hash值肯定为正值,方便后面添加元素判断节点类型
        int hash = spread(key.hashCode());
        //统计每个桶上的元素个数,如果超过8个,会转换成红黑树
        int binCount = 0;
        //死循环,只能通过break跳出循环
        for (Node<K,V>[] tab = table;;) {
        	//f:当前命中的数组索引位置上的元素
        	//n:数组长度
        	//i:数组索引位置
        	//fh:f的hash值
            Node<K,V> f; int n, i, fh;
            //数组还未创建
            if (tab == null || (n = tab.length) == 0)
            	//初始化,创建数组
                tab = initTable();
            //先决条件:数组已经创建好了
            //当前命中的数组元素为空
            else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
            	//以CAS方式在tab[i]上创建一个Node节点,节点中存储了hash值,key,value值,并且next指向null。
            	//添加成功,跳出循环。
            	//添加失败,说明有其他线程已经添加进去值了,只能重新进入for循环
                if (casTabAt(tab, i, null,
                             new Node<K,V>(hash, key, value, null)))
                    break;                   // no lock when adding to empty bin
            }
            //先决条件:数组已经创建好了且当前命中的数组元素不为空
            //当前命中的数组元素hash值为-1,说明正在扩容
            else if ((fh = f.hash) == MOVED)
            	//当前线程协助扩容
                tab = helpTransfer(tab, f);
            //先决条件:数组已经创建好了,当前命中的数组元素不为空,且数组没有在扩容
            //接下来就要形成链表
            else {
                V oldVal = null;
                //f:当前线程命中的数组元素
                //对该元素单独加锁,不会影响数组中其他元素的任何操作
                synchronized (f) {
                	//防止其他线程添加数据后,使当前位置变为红黑树结构(红黑树结构会通过左旋或右旋改变该位置元素),
                	//或该数组被其他线程扩容了,再次确认该位置上是否还是原来的元素
                    if (tabAt(tab, i) == f) {
                        //hash值>=0,表示是链表结构
                        if (fh >= 0) {
                        	//标记该位置上元素个数为1
                            binCount = 1;
                            //遍历链表
                            for (Node<K,V> e = f;; ++binCount) {
                                K ek;
                                //判断当前节点与插入节点的key是否相等
                                //true:将插入节点的value覆盖当前节点的value
                                //false:判断当前节点的下一个节点是否为空
                                if (e.hash == hash &&
                                    ((ek = e.key) == key ||
                                     (ek != null && key.equals(ek)))) {
                                    oldVal = e.val;
                                    if (!onlyIfAbsent)
                                        //插入节点的value覆盖当前节点的value
                                        e.val = value;
                                    //跳出循环
                                    break;
                                }
                                //下面开始判断当前节点的下一个节点是否为空
                                //Node<K,V> pred = e和e = e.next组合起来是将下一个节点设置为当前节点
                                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;
                            }
                        }
                    }
                }
                //如果数组在扩容或是树结构使元素位置发生变化,binCount == 0
                if (binCount != 0) {
                	//如果链表个数>=8,则将其转化为树
                    if (binCount >= TREEIFY_THRESHOLD)
                        treeifyBin(tab, i);
                    //返回被覆盖的旧值
                    if (oldVal != null)
                        return oldVal;
                    break;
                }
            }
        }
        //数据添加完成后,要维护数组长度,还要判断数组是否需要扩容
        addCount(1L, binCount);
        return null;
    }

2.1.2 数组初始化,initTable()方法

//数组初始化。创建数组
private final Node<K,V>[] initTable() {
        //tab:数组
        //sc:期望值
        Node<K,V>[] tab; int sc;
        //再次确认数组是否为空,防止其他线程已经创建好数组
        while ((tab = table) == null || tab.length == 0) {
            //因为此时还未创建好数组,所以数组不可能在扩容
            //true:sc = -1,数组正在初始化
            //false:sc = 0或sc = 数组初始容量(根据上面分析构造器那块可知)
            if ((sc = sizeCtl) < 0)
                //当前线程放弃
                Thread.yield(); // lost initialization race; just spin        
            //以CAS方式修改sizeCtl的值,修改成功,sizeCtl = -1,下面开始正式初始化操作  
            //修改失败,其他线程正在初始化,重新进入循环
            else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
                try {
                	//其他线程可能已经执行过初始化,使sizeCtl = sc=数组扩容时阈值,
                	//这样的话,也可以CAS修改成功,再次判断可以防止重复初始化
                    if ((tab = table) == null || tab.length == 0) {
                        //sc > 0:根据传入的参数将数组初始容量转换为2的n次幂
                        //sc = 0:数组初始容量为默认值16
                        int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
                        @SuppressWarnings("unchecked")
                        //数组长度为默认长度16
                        Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
                        table = tab = nt;
                        //sc = 0.75 * n,表示数组扩容时的阈值
                        sc = n - (n >>> 2);
                    }
                } finally {
                    sizeCtl = sc;
                }
                //初始化完成后,跳出循环
                break;
            }
        }
        return tab;
    }

2.2 图解

在这里插入图片描述

三、jdk1.8扩容安全

3.1 addCount():数据添加完成后,要维护数组长度,还要判断数组是否需要扩容的方法

//数据添加完成后,要维护数组长度,还要判断数组是否需要扩容
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;
            //计数统计,将base和cell[]中的值都加起来
            s = sumCount();
        }
//==========以上属于对添加进去的元素计数统计,借用了LongAdder的源码===================
//======================以下是判断是否需要扩容的代码===============================        
        //如果是添加数据,一定大于0
        if (check >= 0) {
            //tab:旧数组
            //nt:扩容后的新数组
            //n:数组长度
            //sc:期望值
            Node<K,V>[] tab, nt; int n, sc;
            //true:条件1-> 超过扩容阈值或sizeCtl为负数表示正在扩容 
            //条件2-> 数组创建好了  条件3-> 数组长度没有达到最大允许值,意味着可以扩容
            //false:直接退出,不需要扩容了	
            while (s >= (long)(sc = sizeCtl) && (tab = table) != null &&
                   (n = tab.length) < MAXIMUM_CAPACITY) {
                //生成扩容标识戳
                int rs = resizeStamp(n);
                //负数,表示有线程正在扩容,sizeCtl的高16位是扩容标识戳,低16位表示扩容线程数量+1
                if (sc < 0) {
                    //条件1:当sc为负数时,右移16位(即sc的高16位),表示扩容标识戳,但条件1为true时,说明数组已经扩容完成了
                    //条件4:新数组为空
                    //条件5:transferIndex <= 0
                    if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
                        sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||
                        transferIndex <= 0)
                        break;
                    //CAS方式修改sizeCtl的值,修改成功,sizeCtl+1,表示扩容的线程数量+1
                    if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))
                        //协助扩容
                        transfer(tab, nt);
                }
                //sc表示扩容阈值
                //以CAS方式修改sizeCtl的值,扩容标识戳左移16位+2
                //在下面的例子中,rs = 1000 0000 0001 1011,
                //如果修改成功,sizeCtl的高16位是扩容标识戳,低16位表示扩容线程数量+1
                else if (U.compareAndSwapInt(this, SIZECTL, sc,
                                             (rs << RESIZE_STAMP_SHIFT) + 2))
                    //扩容操作
                    transfer(tab, null);
                //计数统计
                s = sumCount();
            }
        }
    }

LongAdder源码分析可参考我的文章: LongAdder源码讲解.

3.1.1 扩容标识戳生成方法

//生成扩容标识戳
static final int resizeStamp(int n) {
		//numberOfLeadingZeros(n):返回无符号整数n最高位非0位前面的0的个数
		//1 << (RESIZE_STAMP_BITS - 1):1左移15位
		//举例:数组默认长度是16,二进制表示是10000,1前边27个0,27用二进制表示为11011
		//1左移15位用二进制表示1000 0000 0000 0000,这个操作会使sizeCtl的最高位为1,sizeCtl就成了负数
		//返回结果是1000 0000 0001 1011
        return Integer.numberOfLeadingZeros(n) | (1 << (RESIZE_STAMP_BITS - 1));
    }

3.2 transfer():扩容操作

3.2.1 扩容基本原理

在多线程环境下,为了提高扩容效率,会把一个数组分割成几块,交由几个任务来迁移,每个任务的最短长度是16。具体实现过程是数组每分割出一块,就由一个线程来完成这一任务。

3.2.2 任务分配图解

为每个线程分配任务的过程图解
在这里插入图片描述
在本图中,如果最后一个任务分得的长度<4,则把剩下的当成一个任务来迁移。

private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {
        //n:旧数组的长度
        //stride:步长
        int n = tab.length, stride;
        //单核cpu情况下,步长为n,多核cpu下,步长最短为16,如果大于16为数组长度/(8 * cpu核数)
        //可以理解成一个长度为n的数组需要扩容,将数组分成几块来迁移,每一块的长度为stride,stride最小为16
        if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)
            stride = MIN_TRANSFER_STRIDE; // subdivide range
        //当执行transfer(tab, null)这个方法时,即第一次发起数据迁移时,nextTab == null
        if (nextTab == null) {            // initiating
            try {
                @SuppressWarnings("unchecked")
                //创建了一个是原来数组两倍大小的数组
                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;
            //指向原数组最后的位置+1
            transferIndex = n;
        }
        //新数组的长度
        int nextn = nextTab.length;
        //建一个forwardingNode节点,用来标记该位置已经被迁移了
        //标记了fwd标志的位置,对应的hash值为-1
        ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab);
        //标记当前线程分配的迁移工作是否完成
        boolean advance = true;
        boolean finishing = false; // to ensure sweep before committing nextTab
        //i:正在迁移的数组元素的索引
        //bound:下一次任务迁移的开始桶位,从后往前
        for (int i = 0, bound = 0;;) {
            Node<K,V> f; int fh;
            //true->当前线程分配的迁移工作已完成
//=============while这一块代码是分配任务的,真正迁移是在下边============================
            while (advance) {
                int nextIndex, nextBound;
                //--i >= bound:当前线程分配的迁移任务还没有完成
                if (--i >= bound || finishing)
                    advance = false;
                //所有数组元素都被分配完了
                else if ((nextIndex = transferIndex) <= 0) {
                    i = -1;
                    advance = false;
                }
                //第一次进来时,只能进下边这个判断
                //如果剩下的任务大于stride,下一个边界等于nextIndex-stride
                //如果剩下任务不够一个步长,只能将下一个边界指向0
             	//分配任务,以CAS方式修改transferIndex的值,
                else if (U.compareAndSwapInt
                         (this, TRANSFERINDEX, nextIndex,
                          nextBound = (nextIndex > stride ?
                                       nextIndex - stride : 0))) {
                    //进行下一个任务分配的准备
                    //将下一个边界赋值给当前边界
                    bound = nextBound;
                    //正在迁移的数组元素索引为nextIndex - 1,表示当前任务从这个索引位置开始迁移
                    i = nextIndex - 1;
                    //标记迁移还未完成
                    advance = false;
                }
            }
 //=======================分配完任务后,开始迁移数据==============================
            //这是最后的判断条件,一开始不会满足这个条件,所有数组元素都被分配了
            if (i < 0 || i >= n || i + n >= nextn) {
                int sc;
                //任务分配完了,还需要判断是否扩容完成了
                //true->扩容完成了
                if (finishing) {
                    nextTable = null;
                    //扩容后的数组赋值给当前数组
                    table = nextTab;
                    //sizeCtl=2n-0.5n = 1.5n = 0.75 *2n,重新计算扩容后的阈值
                    sizeCtl = (n << 1) - (n >>> 1);
                    return;
                }
                //所有数组元素都被分配了,当前线程没用了,所以当前线程会退出,sizeCtl-1,线程数量-1
                if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) {
                    //sizeCtl在第一次迁移前,会通过resizeStamp(n) << RESIZE_STAMP_SHIFT + 2计算出sizeCtl,
                    //当有线程来协助扩容时,sizeCtl+1。
                    //当下面这个条件不相等时,这个线程直接退出。
                    //相等时,说明这是最后一个线程,标记扩容完成,
                    if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT)
                        return;
                    finishing = advance = true;
                    i = n; // recheck before commit
                }
            }
            //当前命中的数组元素为空
            else if ((f = tabAt(tab, i)) == null)
                //不用迁移,直接以CAS方式修改为fwd节点
                advance = casTabAt(tab, i, null, fwd);
            //当前线程命中的数组元素的hash值为-1,表示这一块任务已经被迁移了,因此,将advance设置为true
            else if ((fh = f.hash) == MOVED)
                advance = true; // already processed
//=====================不为空,也不为-1,真正的迁移数据===========================
            else {
            	//只对当前线程命中的数组元素加锁,其他元素的任何操作都不受影响
                synchronized (f) {
                    //再次确认,防止其他线程扩容时将该元素转移到其他位置
                    if (tabAt(tab, i) == f) {
                        //当第一次求数组的索引位置时,用hash&(n-1),但当数组扩容时,
                        //为了提高效率,可以直接判断hash值的第x位。(x的求法举例说明)
                        //举例:数组长度为16时,n-1用二进制表示为1111,用4位二进制数即可表示,
                        //当扩容成32时,n-1表示为11111,5位二进制数表示,那么可以直接判断hash值的从低位往高位数第5位是否为0,
                        //如果为0,该数据还在该位置,如果为1,该数据在该位置+原数组长度。
                       	//ln表示扩容后还在该位置的节点,hn表示在该索引位置+数组长度
                        Node<K,V> ln, hn;
                        //表示是链表结构
                        if (fh >= 0) {
                            //如果hash值第x位为1,runbit最高位为1,其余位为0
                            //如果hash值第x位为0,runbit所有位都是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条件是为了让链表最后几个节点如果是同类的话(指他们扩容后索引位置一样),
                                //不破坏此结构,直接将同类的头节点连接过去
                                if (b != runBit) {
                                    runBit = b;
                                    lastRun = p;
                                }
                            }
                            //==0表示扩容后还在该位置,赋值给ln
                            if (runBit == 0) {
                                ln = lastRun;
                                hn = null;
                            }
                            else {
                                hn = lastRun;
                                ln = null;
                            }
                            //循环,形成ln链和hn链
                            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);
                            //将原数组的i位置设置为fwd节点
                            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;
                            //将ln链表放在 i 位置也就是不动 
                            setTabAt(nextTab, i, ln);
                            //将hn链表放在 i+n 位置 
                            setTabAt(nextTab, i + n, hn);
                            //将i这个索引位置元素设置为fwd节点,表示已经迁移过了
                            setTabAt(tab, i, fwd);
                            advance = true;
                        }
                    }
                }
            }
        }
    }

关于为什么能设置ln表示扩容后位置不变,hn表示扩容后原索引位置+原数组长度,可以参考我的文章: ConcurrentHashMap容量为什么是2的n次幂.

3.2.3 链表迁移过程图解

在这里插入图片描述
因此,最终,runBit为蓝色,lastRun指向倒数第三个,不破坏最后三个的结构,他们扩容后索引位置一样,直接将头节点连接过去,提高迁移的效率。

四、jdk1.8多线程扩容效率改进

多线程协助扩容会在两个地方被触发:
1、当添加元素时,对应的索引位置是fwd节点,说明正在扩容,那么协助扩容,协助扩容后再添加元素
2、添加完元素后,如果sizeCtl为负数且数组不为空,那么协助扩容。

//第一种情况:
final V putVal(K key, V value, boolean onlyIfAbsent) {
        if (key == null || value == null) throw new NullPointerException();
        int hash = spread(key.hashCode());
        int binCount = 0;
        for (Node<K,V>[] tab = table;;) {
            Node<K,V> f; int n, i, fh;
            //数组还未创建
            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 Node<K,V>(hash, key, value, null)))
                    break;                   // no lock when adding to empty bin
            }
            //先决条件:数组已经创建好了且当前命中的数组元素不为空
            //当前命中的数组元素hash值为-1,说明正在扩容
            else if ((fh = f.hash) == MOVED)
            	//当前线程协助扩容
                tab = helpTransfer(tab, f);
//第二种情况:
private final void addCount(long x, int check) {
       ........
       ........
//======================以下是判断是否需要扩容的代码===============================        
        //如果是添加数据,一定大于0
        if (check >= 0) {
            Node<K,V>[] tab, nt; int n, sc;
            //true:条件1-> 超过扩容阈值或sizeCtl为负数表示正在扩容 
            //条件2-> 数组创建好了  条件3-> 数组长度没有达到最大允许值,意味着可以扩容
            //false:直接退出,不需要扩容了	
            while (s >= (long)(sc = sizeCtl) && (tab = table) != null &&
                   (n = tab.length) < MAXIMUM_CAPACITY) {
                //生成扩容标识戳
                int rs = resizeStamp(n);
                //负数,表示有线程正在扩容,sizeCtl的高16位是扩容标识戳,低16位表示扩容线程数量+1
                if (sc < 0) {
                    if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
                        sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||
                        transferIndex <= 0)
                        break;
                    //CAS方式修改sizeCtl的值,修改成功,sizeCtl+1,表示扩容的线程数量+1
                    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);
                //计数统计
                s = sumCount();
            }
        }
    }
  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ConcurrentHashMap 1.8 是 Java 中线程安全的哈希表实现,它是 Java 5 中引入的 ConcurrentHashMap 的升级版本。以下是 ConcurrentHashMap 1.8 的主要代码实现: 1. 分段锁实现 ConcurrentHashMap 1.8 在内部使用了分段锁(Segment)来实现线程安全。每个 Segment 都是一个独立的哈希表,拥有自己的锁,不同线程可以同时访问不同的 Segment,从而提高并发性能。 2. 数据结构 ConcurrentHashMap 1.8 内部的数据结构是一个 Segment 数组,每个 Segment 内部是一个哈希表,哈希表中的每个元素是一个链表或红黑树。 3. put 操作实现 ConcurrentHashMap 1.8 的 put 操作首先会根据 Key 的哈希值和哈希表的长度计算出元素应该存放在哪个 Segment 中,然后在该 Segment 的哈希表中查找元素。 如果元素已经存在,就直接替换其值;否则就创建一个新的节点并添加到链表或红黑树中。如果链表或红黑树的长度超过了一定阈值,就会将链表转化为红黑树,从而提高查找效率。 4. get 操作实现 ConcurrentHashMap 1.8 的 get 操作与 put 操作类似,首先会根据哈希值和哈希表长度计算出元素所在的 Segment,然后在该 Segment 的哈希表中查找元素。 如果元素存在于链表中,就顺序查找;如果存在于红黑树中,就使用红黑树的查找算法。如果找到了元素,就返回其值;否则返回 null。 5. 扩容实现 ConcurrentHashMap 1.8 的扩容过程与 ConcurrentHashMap 1.7 相比,更加高效。它采用了一种“分段锁粒度更细”的方式,只对需要扩容的 Segment 进行加锁,其他 Segment 可以继续访问,从而提高并发性能。 6. 其他实现细节 ConcurrentHashMap 1.8 还实现了一些其他的细节,例如使用 Unsafe 类来实现 CAS 操作、使用 ThreadLocalRandom 来生成哈希值等,从而保证了其高性能和线程安全性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值