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;
}
}
}
}
}
}