美文网首页
ConcurrentHashMap(gold_axe)

ConcurrentHashMap(gold_axe)

作者: 胖达_4b7e | 来源:发表于2020-10-30 00:48 被阅读0次

    构造 指定初始容量

    jdk1.8初始容量

    1.8有变化 比如设的32 是 32+32/2+1 向上取2的次方
    所有 设32 得 64
    ↓ 向上取2的次方 是和hashMap一样的

        private static final int tableSizeFor(int c) {
            int n = c - 1;
            n |= n >>> 1;
            n |= n >>> 2;
            n |= n >>> 4;
            n |= n >>> 8;
            n |= n >>> 16;
            return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
        }
    

    sizeCtl

    正数 :
    数组没有初始化,记录的是数组初始容量
    数组已经初始化,记录的是数组的扩容阈值

        /**
         * Table initialization and resizing control.  When negative, the
         * table is being initialized or resized: -1 for initialization,
         * else -(1 + the number of active resizing threads).  Otherwise,
         * when table is null, holds the initial table size to use upon
         * creation, or 0 for default. After initialization, holds the
         * next element count value upon which to resize the table.
         */
        private transient volatile int sizeCtl;
    
    • 0 数组没有初始化
      负数 是正进行某事
    • -1 代表数组正在初始化
    • 不是-1的负数,表示数组正在扩容

    没有参数的构造方法, 啥都没有干 当然sizeCtl是0, 数组没初始化

        public ConcurrentHashMap() {
        }
    
    

    put

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

    默认是覆盖旧值
    可以看到 kv 都不支持null↓


    第一次put会初始化数组

    initTable 一个漂亮的cas无锁操作, 初始化数组

    第一次进来 sizeCtl 默认是0 走cas, 把sizeCtl 值变成-1(正在初始化)
    这里 SIZECTL 是sizeCtl 底层地址
    sizeCtl <0 时要么是在扩容 要么是在初始化, 那么遇到就没啥好做了的了 yield

    再看cas操作成功后, 正常这种Cas-1成功是代表抢到了 初始化的权限


    双重检测

    为什么双重检查:
    如果第二个线程进去while的时候还没初始化好, 但是检查sizeCtl 的时候已经初始化好了,sizeCtl >0,
    这样的话 还是会去cas 改回-1
    这个时候就不用扩容了, 在finally里面把被cas调的扩容阈值放回去了好了

    初始化数组的同时, 会设好下次扩容 的阈值:
    sc = n - (n >>> 2); 其实就是 0.75*n
    初始化完成后, sizeCtl存的是扩容阈值

    casTabAt 填入值


    putVal方法主体是一个死循环, 如果没初始化数组, 会先进去initTable初始化数组,
    初始化结束后, 又循环,
    明显整个数组都是空的,进入 casTabAt , cas 成功把值放进去, 就能break 出循环
    如果cas失败, 就是hash 碰撞了, 那么 就继续循环, 会进最后一个else

    helpTransfer


    如果是扩容中, 就帮助扩容,
    转移到新数组的数据, 原来数组会填个-1

    hash冲突!!~synchronized !


    synchronized 只锁了链表的头


    有个双重校验 这里,成立 说明还没转树 已有值 覆盖, 没有值 连最后面

    for循环出来以后, 保证值已经放进去了,


    binCount 记的这个链表的长度
    addCount(1写死,链表的长度 )

    addCount 计数 扩容

    如果 cas把计数器++的操作失败的话, 就是因为现在有多线程加值, 按多线程的处理方法, 就是每个线程一个格子加, 总数的计数器加每个格子


    就会进去fullAddCount
    sumCount:
    是把 baseCount 加上 多线程计数数组counterCells 元素之和

    得到最新元素个数统计以后,就扩容


    这里面扩容

    fullAddCount 多线程计数 用个数组记


    计数锁是这么用的


    ↑第一次会进去这里:
    cellsBusy == 0 int初始值就是0
    counterCells == as 都没初始化 都是null
    U.compareAndSwapInt(this, CELLSBUSY, 0, 1) cas做 cellsBusy 赋值为1(本来是0)

    里面就是新建了一个里面存数字的数组, 其中一个格子填进去1, 然后就可以跳出循环 结束fullAddCount

    如果 计数数组已经初始化过了, 第二次进 ↓




    x等于1
    如果这个计数格子是空的, 填入1
    如果不是空 原来的值+1

    如果 计数格子+1 这个cas操作 不成功 有2种后续解决
    1.不能扩容了, 已经很多格子了, 就自旋, 这里一个改成false 一个改成true, 就拦住了, 进不去下面的else if了, 会再循环 cas 格子计数+1


    1. 没到计数数组的大小极限, 就扩容


    扩容


    transfer

    transfer

    帮助扩容也会来这
    至少会把新数组分成16段, 就是16个任务


    每个老数组里面的头迁移好了, 就赋值-1

    这个有个迁移数据的改进
    假设现在迁移的位置 是个链表 size是6 那么可能还是3个保留在原来的位置 还有3个放置到原来的位置+老数组的长度位置上

    相关文章

      网友评论

          本文标题:ConcurrentHashMap(gold_axe)

          本文链接:https://www.haomeiwen.com/subject/bhclvktx.html