ConcurrentHashMap(gold_axe)

构造 指定初始容量

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个放置到原来的位置+老数组的长度位置上

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。