数组中 Node 节点的 Hash 值几种常量:
// hash>0表示数组中是链表,hash=0表示null
static final int MOVED = -1; // Map 在扩容,旧数组中对应节点已经被到了新数组中
static final int TREEBIN = -2; // 数组对应节点是红黑树(与HashMap不同,hashMap数组中存放的是红黑树的根节点,
// 而ConcurrentHashMap给红黑树包了一层,放的是TreeBin对象,hash值为-2,其中有一个指针指向红黑树的根节点)
static final int RESERVED = -3; // hash for transient reservations
static final int HASH_BITS = 0x7fffffff; // usable bits of normal node hash
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(); // k,v不能为空
int hash = spread(key.hashCode()); // 计算hash值
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) { //数组对应位置的值为null,tabAt方法保证调用时的可见性
if (casTabAt(tab, i, null,
new Node<K,V>(hash, key, value, null))) // cas 往里设置值
break; // no lock when adding to empty bin
}
else if ((fh = f.hash) == MOVED) // Map正在扩容
tab = helpTransfer(tab, f);
else {
V oldVal = null;
synchronized (f) { // 头节点作为锁对象
if (tabAt(tab, i) == f) {
if (fh >= 0) {
binCount = 1;
for (Node<K,V> e = f;; ++binCount) { // 遍历链表
K ek;
if (e.hash == hash &&
((ek = e.key) == key ||
(ek != null && key.equals(ek)))) { // 链表中存在相同的值
oldVal = e.val;
if (!onlyIfAbsent)
e.val = value;
break;
}
Node<K,V> pred = e;
if ((e = e.next) == null) { // 遍历到链表尾部,插入
pred.next = new Node<K,V>(hash, key,
value, null);
break;
}
}
//HashMap:数组中是放入了红黑树的root节点,TreeNode类型
//ConcurrentHashMap:数组中是放入了TreeBin对象,TreeBin对象中有一个root属性,指向红黑树root节点
//相当于是给红黑树在外面包了一层,方便用数组中的节点加锁,避免了HashMap红黑树root节点会变动问题
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;
}
}
}
}
if (binCount != 0) {
if (binCount >= TREEIFY_THRESHOLD) //链表数量大于等于8时,转红黑树
treeifyBin(tab, i);
if (oldVal != null)
return oldVal;
break;
}
}
}
addCount(1L, binCount);
return null;
}
private final Node<K,V>[] initTable() {
Node<K,V>[] tab; int sc;
while ((tab = table) == null || tab.length == 0) {
if ((sc = sizeCtl) < 0) // 说明正在有线程初始化
Thread.yield(); //让出cpu资源
else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) { //cas获取锁
try {
if ((tab = table) == null || tab.length == 0) {
int n = (sc > 0) ? sc : DEFAULT_CAPACITY; //默认容量16
@SuppressWarnings("unchecked")
Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n]; //初始化数组
table = tab = nt;
sc = n - (n >>> 2); //n右移2位相当于16/4(容量的1/4),然后再用容量减掉,就剩下3/4了,和HashMap的0.75一样
}
} finally {
sizeCtl = sc;
}
break;
}
}
return tab;
}
private final void treeifyBin(Node<K,V>[] tab, int index) {
Node<K,V> b; int n, sc;
if (tab != null) {
if ((n = tab.length) < MIN_TREEIFY_CAPACITY) //数组大小小于64,扩容
tryPresize(n << 1);
else if ((b = tabAt(tab, index)) != null && b.hash >= 0) {
synchronized (b) {
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); //Node节点转换成TreeNode节点
// 构建双向链表
if ((p.prev = tl) == null)
hd = p;
else
tl.next = p;
tl = p;
}
setTabAt(tab, index, new TreeBin<K,V>(hd)); //构建红黑树并放入数组中
}
}
}
}
}
private final void tryPresize(int size) {
int c = (size >= (MAXIMUM_CAPACITY >>> 1)) ? MAXIMUM_CAPACITY :
tableSizeFor(size + (size >>> 1) + 1);
int sc;
while ((sc = sizeCtl) >= 0) {
Node<K,V>[] tab = table; int n;
if (tab == null || (n = tab.length) == 0) { // 数组初始化,与initTable()方法相似
n = (sc > c) ? sc : c;
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 = sc;
}
}
}
else if (c <= sc || n >= MAXIMUM_CAPACITY) //扩容完毕
break;
else if (tab == table) { //多线程扩容,与addCount()方法中逻辑相同
int rs = resizeStamp(n);
if (sc < 0) {
Node<K,V>[] nt;
if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||
transferIndex <= 0)
break;
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);
}
}
}
与 HashMap
中 treeify()
的处理一样,就不再赘述,详见:HashMap put() 方法源码分析#treeify()
// b 是双向链表的头节点
TreeBin(TreeNode<K,V> b) {
super(TREEBIN, null, null, null); //设置Hash值为-2,表示一颗红黑树
this.first = b;
TreeNode<K,V> r = null;
for (TreeNode<K,V> x = b, next; x != null; x = next) {
next = (TreeNode<K,V>)x.next;
x.left = x.right = null;
if (r == null) { //根节点的情况
x.parent = null;
x.red = false;
r = x;
}
else {
K k = x.key;
int h = x.hash;
Class<?> kc = null;
for (TreeNode<K,V> p = r;;) {
int dir, ph;
K pk = p.key;
if ((ph = p.hash) > h)
dir = -1;
else if (ph < h)
dir = 1;
else if ((kc == null &&
(kc = comparableClassFor(k)) == null) ||
(dir = compareComparables(kc, k, pk)) == 0)
dir = tieBreakOrder(k, pk);
TreeNode<K,V> xp = p;
if ((p = (dir <= 0) ? p.left : p.right) == null) {
x.parent = xp;
if (dir <= 0)
xp.left = x;
else
xp.right = x;
r = balanceInsertion(r, x); // 与HashMap处理相同
break;
}
}
}
}
this.root = r;
assert checkInvariants(root);
}
final TreeNode<K,V> putTreeVal(int h, K k, V v) {
// 前面的代码和HashMap的代码一样,就不赘述了
Class<?> kc = null;
boolean searched = false;
for (TreeNode<K,V> p = root;;) {
int dir, ph; K pk;
if (p == null) {
first = root = new TreeNode<K,V>(h, k, v, null, null);
break;
}
else if ((ph = p.hash) > h)
dir = -1;
else if (ph < h)
dir = 1;
else if ((pk = p.key) == k || (pk != null && k.equals(pk)))
return p;
else if ((kc == null &&
(kc = comparableClassFor(k)) == null) ||
(dir = compareComparables(kc, k, pk)) == 0) {
if (!searched) {
TreeNode<K,V> q, ch;
searched = true;
if (((ch = p.left) != null &&
(q = ch.findTreeNode(h, k, kc)) != null) ||
((ch = p.right) != null &&
(q = ch.findTreeNode(h, k, kc)) != null))
return q;
}
dir = tieBreakOrder(k, pk);
}
TreeNode<K,V> xp = p;
if ((p = (dir <= 0) ? p.left : p.right) == null) {
TreeNode<K,V> x, f = first;
first = x = new TreeNode<K,V>(h, k, v, f, xp);
if (f != null)
f.prev = x;
if (dir <= 0)
xp.left = x;
else
xp.right = x;
if (!xp.red) //父节点是黑的,无需加锁,直接插入
x.red = true;
else {
lockRoot(); //加写锁
try {
root = balanceInsertion(root, x);
} finally {
unlockRoot();
}
}
break;
}
}
assert checkInvariants(root);
return null;
}
final Node<K,V>[] helpTransfer(Node<K,V>[] tab, Node<K,V> f) {
Node<K,V>[] nextTab; int sc;
if (tab != null && (f instanceof ForwardingNode) &&
(nextTab = ((ForwardingNode<K,V>)f).nextTable) != null) {
int rs = resizeStamp(tab.length);
while (nextTab == nextTable && table == tab &&
(sc = sizeCtl) < 0) {
if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
sc == rs + MAX_RESIZERS || transferIndex <= 0)
break;
if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) {
transfer(tab, nextTab);
break;
}
}
return nextTab;
}
return table;
}
private final void addCount(long x, int check) { //x:1;check:链表长度或者红黑树是2
CounterCell[] as; long b, s;
if ((as = counterCells) != null ||
!U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) { //尝试直接往baseCount中加值
// CounterCell数组不为空(说明有竞争) 或者 直接往baseCount中加值失败(cas失败)
CounterCell a; long v; int m;
boolean uncontended = true;
if (as == null || (m = as.length - 1) < 0 || // 数组为空
(a = as[ThreadLocalRandom.getProbe() & m]) == null || //获取线程的随机数 & 数组长度求出对应线程的数组下标,as数组中对应下标的元素为null
!(uncontended =
U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) { //cas往as数组中对应位置加1
fullAddCount(x, uncontended); //CAS往里加数组里加1
return; //进入这个if虽然加了1,但是不会走后面的扩容逻辑了(有冲突时不执行扩容,没冲突且达到阈值时才扩容)
}
if (check <= 1)
return;
s = sumCount(); //算出当前map中元素个数
}
if (check >= 0) {
Node<K,V>[] tab, nt; int n, sc;
while (s >= (long)(sc = sizeCtl) && (tab = table) != null && //大于阈值(sizeCtl)时扩容
(n = tab.length) < MAXIMUM_CAPACITY) {
int rs = resizeStamp(n);
if (sc < 0) { // sc是负数说明正在扩容,与helpTransfer()方法逻辑相同
if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||
transferIndex <= 0) //当满足这些条件时,表示扩容完毕了
break;
if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) //每有一个后续线程帮助扩容时,sc就+1
transfer(tab, nt);
}
else if (U.compareAndSwapInt(this, SIZECTL, sc,
(rs << RESIZE_STAMP_SHIFT) + 2)) //注意,第一个线程执行扩容时,会把sc设置成负数并+2(初始值)
transfer(tab, null);
s = sumCount();
}
}
}
ThreadLocalRandom.getProbe()
方法详解:关于 ConcurrentHashMap 1.8 中的线程探针哈希
//wasUncontended字段用来标识当前线程获取的probe通过计算后,在CounterCell数组中对应下标是否能加成功
private final void fullAddCount(long x, boolean wasUncontended) { //x:1;wasUncontended:false
int h;
if ((h = ThreadLocalRandom.getProbe()) == 0) { //获取线程的hash值(多次调用值一样)
ThreadLocalRandom.localInit(); //初始化
h = ThreadLocalRandom.getProbe();
wasUncontended = true;
}
boolean collide = false; // true时表示发生冲突,数组要执行扩容
for (;;) { //自旋
CounterCell[] as; CounterCell a; int n; long v;
if ((as = counterCells) != null && (n = as.length) > 0) { // 数组不是空的,已经完成了初始化
if ((a = as[(n - 1) & h]) == null) { //数组当前下标位置是空的
if (cellsBusy == 0) { // Try to attach new Cell
CounterCell r = new CounterCell(x); // 初始化数组中对应位置的元素,且初始值为1
if (cellsBusy == 0 &&
U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) { // 数组cas加锁
boolean created = false;
try { // Recheck under lock
CounterCell[] rs; int m, j;
if ((rs = counterCells) != null &&
(m = rs.length) > 0 &&
rs[j = (m - 1) & h] == null) { //再次判断
rs[j] = r;
created = true;
}
} finally { //释放锁
cellsBusy = 0;
}
if (created) //创建成功
break;
continue; // 继续循环(用continue跳过了修改线程的probe值)
}
}
collide = false;
}
else if (!wasUncontended) // false表示上一次cas+1的时候失败了
wasUncontended = true; // 刷新状态为true,并修改线程probe的值(跳出if后)
else if (U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x)) //再尝试cas+1,成功则跳出循环,失败则修改线程probe的值
break;
else if (counterCells != as || n >= NCPU) //数组扩容后或者数组长度大于cpu核数时,不允许扩容
collide = false; // At max size or stale
else if (!collide) //CAS失败,说明发生冲突,collide设置为true,下次循环过来collide 为true时,执行扩容
collide = true;
else if (cellsBusy == 0 &&
U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) { //数组加锁,扩容
try {
if (counterCells == as) {// Expand table unless stale
CounterCell[] rs = new CounterCell[n << 1]; //创建新数组,容量*2
for (int i = 0; i < n; ++i)
rs[i] = as[i]; //旧数组元素放新数组中
counterCells = rs;
}
} finally {
cellsBusy = 0;
}
collide = false;
continue; // Retry with expanded table
}
h = ThreadLocalRandom.advanceProbe(h); //给线程生成一个新的Probe值(随机数)
}
else if (cellsBusy == 0 && counterCells == as && //cellsBusy标识数组是否被占用,0表示没有占用
U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) { //cas将cellsBusy改为1(加锁),保证只有一个线程能对cells数组初始化
boolean init = false;
try { // Initialize table
if (counterCells == as) {
CounterCell[] rs = new CounterCell[2]; //初始化数组大小为2
rs[h & 1] = new CounterCell(x); //对应位置直接+1
counterCells = rs;
init = true; //初始化完毕
}
} finally {
cellsBusy = 0;
}
if (init) //初始化完毕,且计数也加了1,就跳出循环
break;
}
else if (U.compareAndSwapLong(this, BASECOUNT, v = baseCount, v + x)) //如果其它情况都被其它线程占用,则尝试对baseCount加1
break; // Fall back on using base
}
}
final long sumCount() {
CounterCell[] as = counterCells; CounterCell a;
long sum = baseCount; //基础值
if (as != null) {
for (int i = 0; i < as.length; ++i) { //遍历CounterCell数组进行统计求和
if ((a = as[i]) != null)
sum += a.value;
}
}
return sum;
}
private static int RESIZE_STAMP_BITS = 16;
private static final int RESIZE_STAMP_SHIFT = 32 - RESIZE_STAMP_BITS;
//resizeStamp相当于把第int的第16位赋值为1(1左移15位是在第16位),然后在addCount方法中向左移动16位时,1到了符号位,所以变成负数了
//RESIZE_STAMP_SHIFT = 32 - RESIZE_STAMP_BITS,所以只要 1 左移RESIZE_STAMP_BITS-1位,再左移RESIZE_STAMP_SHIFT位
//(共左移了31位,1到了第32位),1就被移到了符号位,成为了负数
static final int resizeStamp(int n) {
return Integer.numberOfLeadingZeros(n) | (1 << (RESIZE_STAMP_BITS - 1));
}
Integer.numberOfLeadingZeros(i)
方法:返回无符号整数 i
的最高非 0
位前面的 0
的个数,包括符号位在内;如果 i
为负数,这个方法将会返回 0
private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) { //tab是旧数组,nextTab是新数组
int n = tab.length, stride;
if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE) //计算步长,(n/8)/NCPU,最小值是16
stride = MIN_TRANSFER_STRIDE; // subdivide range
if (nextTab == null) { // 新数组为空,初始化
try {
@SuppressWarnings("unchecked")
Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n << 1]; //扩容,*2
nextTab = nt;
} catch (Throwable ex) { // try to cope with OOME
sizeCtl = Integer.MAX_VALUE;
return;
}
nextTable = nextTab;
transferIndex = n; //transferIndex:多线程下,数组从后往前遍历挨个元素转换,transferIndex 表示下个线程应该转换旧数组的哪个元素
}
int nextn = nextTab.length;
ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab); //ForwardingNode是个特殊的Node对象,hash值为MOVED(-1)
boolean advance = true; //当前线程是否继续往前找未转移的元素
boolean finishing = false; // 当前线程的扩容逻辑是否做完(只是当前线程)
for (int i = 0, bound = 0;;) {
Node<K,V> f; int fh;
while (advance) {
int nextIndex, nextBound; //nextIndex是跳过的右边界,nextBound是跳过的左边界,左闭右开
//不越界的情况下,nextIndex - nextBound = stride(步长)
if (--i >= bound || finishing) //倒着遍历
advance = false;
else if ((nextIndex = transferIndex) <= 0) { //旧数组已经转换到下标0的位置了
i = -1;
advance = false;
}
else if (U.compareAndSwapInt
(this, TRANSFERINDEX, nextIndex,
nextBound = (nextIndex > stride ?
nextIndex - stride : 0))) { //数组是从右往左遍历,如果越界(负数),下标就设置为0
bound = nextBound;
i = nextIndex - 1;
advance = false;
}
}
if (i < 0 || i >= n || i + n >= nextn) {
int sc;
if (finishing) {
nextTable = null;
table = nextTab; //转移到新的数组
sizeCtl = (n << 1) - (n >>> 1); // *2*0.75
return;
}
if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) { //每有一个线程帮助扩容完,sc就-1
if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT)
//这个逻辑相当于: sc != (resizeStamp(n) << RESIZE_STAMP_SHIFT) + 2 ,
//(resizeStamp(n) << RESIZE_STAMP_SHIFT) + 2 是第一个线程执行扩容时,sc 设置的初始值,之后每有一个线程帮忙扩容,就在 sc 的初始值上 + 1
return; //当前线程扩容执行完毕
// 当 sc == (resizeStamp(n) << RESIZE_STAMP_SHIFT) + 2 时,会走到这儿,表示除了当前线程,其它所有帮助扩容的线程都执行完了
finishing = advance = true; //表示所有线程扩容执行完毕
i = n; // recheck before commit
}
}
else if ((f = tabAt(tab, i)) == null) //数组对应位置是null
advance = casTabAt(tab, i, null, fwd);
else if ((fh = f.hash) == MOVED) //数组对应位置已被其它线程处理
advance = true; // 循环处理前一个位置
else {
synchronized (f) {
if (tabAt(tab, i) == f) {
Node<K,V> ln, hn;
if (fh >= 0) { //数组中对应位置是链表
// 链表在扩容时会分成高位和低位(和HashMap相同),假设单向链表最后一位是高位,然后往前推,反向遍历,runBit就指向链表节点第一次变成低位时的后一个高位节点
// 即在原链表中,runBit指向的节点为头的单向链表,要么都是低位的,要么都是高位的
int runBit = fh & n; //指向链表最后相同位的节点
Node<K,V> lastRun = f;
for (Node<K,V> p = f.next; p != null; p = p.next) { //遍历链表,找出runBit节点的位置
int b = p.hash & n;
if (b != runBit) {
runBit = b;
lastRun = p;
}
}
if (runBit == 0) { //将runBit后的节点整个挪到新数组
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); //往旧数组中设置fwd
advance = true;
}
else if (f instanceof TreeBin) { //如果数组中的元素是红黑树,与HashMap逻辑相同
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) : //小于6,红黑树退化成链表
(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;
}
}
}
}
}
}
ForwardingNode(Node<K,V>[] tab) {
super(MOVED, null, null, null);
this.nextTable = tab;
}
主要利用 Unsafe
操作 + synchronized
关键字。
Unsafe
操作的使用主要负责并发安全的修改对象的属性或数组某个位置的值。synchronized
主要负责在需要操作某个位置时进行加锁(该位置不为空),比如向某个位置的链表进行插入结点,向某个位置的红黑树插入结点。JDK8中仍然有分段锁的思想,JDK8中数组的每一个位置都有一把锁。
当向 ConcurrentHashMap
中 put
一个 key
,value
时,
首先根据 key
计算对应的数组下标 i
,如果该位置没有元素,则通过自旋的方法去向该位置赋值。
如果该位置有元素,则 synchronized
会加锁
加锁成功之后,在判断该元素的类型
a. 如果是链表节点则进行添加节点到链表中
b. 如果是红黑树则添加节点到红黑树
添加成功后,判断是否需要进行树化
addCount()
,这个方法的意思是 ConcurrentHashMap
的元素个数加 1
,但是这个操作也是需要并发安全的,并且元素个数加 1
成功后,会继续判断是否要进行扩容,如果需要,则会进行扩容,所以这个方法很重要。
同时一个线程在 put
时如果发现当前 ConcurrentHashMap
正在进行扩容则会去帮助扩容。