构造 指定初始容量
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
-
没到计数数组的大小极限, 就扩容
扩容
在
transfer
transfer
帮助扩容也会来这
至少会把新数组分成16段, 就是16个任务
每个老数组里面的头迁移好了, 就赋值-1
这个有个迁移数据的改进
假设现在迁移的位置 是个链表 size是6 那么可能还是3个保留在原来的位置 还有3个放置到原来的位置+老数组的长度位置上
网友评论