Java8-ConcurrentHashMap特点
1.使用了懒加载模式,在第一次put数据时,会执行初始化操作,初始化大小默认为16
2.使用了数组+链表+红黑树的方式存储数据
3.使用了CAS+Synchronize来并发控制put、remove操作,对于get 读操作是没有添加锁的
4.支持多线程操作,并发控制,对于同一桶进行操作需要取得锁才能访问(put, remove)
下面冲put实现来一一分析一下
重要属性
// ForwardingNode节点的哈希值
static final int MOVED = -1; // hash for forwarding nodes
// 红黑树根节点哈希值
static final int TREEBIN = -2; // hash for roots of trees
// CPU核心个数
static final int NCPU = Runtime.getRuntime().availableProcessors();
/** 控制标识符,不同的值表示不同的意义,默认值为0:
* -1代表数组正在进行初始化
* -N代表有N-1个线程正在进行扩容操作????
* 正数代表下一次需要进行扩容时的阈值,也就是当前容量的0.75(负载因子)倍
*/
private transient volatile int sizeCtl;
// 数据迁移时的索引
private transient volatile int transferIndex;
// 存放数据的数组,大小始终为2的整数次幂
transient volatile Node[] table;
// 只有在扩容时才用得到,用于存储扩容后的数据,因此只有在扩容时nextTable才非空
private transient volatile Node[] nextTable;
//
private transient volatile long baseCount;
// 数据迁移时每个线程每次迁移数据的最小步长
private static final int MIN_TRANSFER_STRIDE = 16;
// 用于计算sizeCtrl时用到的生成戳,最小为6
private static int RESIZE_STAMP_BITS = 16;
// 可以帮助扩容的最大线程数
private static final int MAX_RESIZERS = (1 << (32 - RESIZE_STAMP_BITS)) - 1;
// 把生成戳保存在sizeCtl中的移位量
private static final int RESIZE_STAMP_SHIFT = 32 - RESIZE_STAMP_BITS;
重要的原子操作
// tab 中获取对应元素
@SuppressWarnings("unchecked")
static final Node tabAt(Node[] tab, int i) {
// 获取obj对象中offset偏移地址对应的object型field的值,支持volatile load语义。
return (Node) U.getObjectVolatile(tab, ((long) i << ASHIFT) + ABASE);
}
// 基于 CAS 思想替换值
static final boolean casTabAt(Node[] tab, int i, Node c, Node v) {
// 如果 tab 中 对应位置元素 C 相等的替换成 V
return U.compareAndSwapObject(tab, ((long) i << ASHIFT) + ABASE, c, v);
}
// 设置数组元素
static final void setTabAt(Node[] tab, int i, Node v) {
// 数组添加元素
U.putObjectVolatile(tab, ((long) i << ASHIFT) + ABASE, v);
}
ConcurrentHashMap初始化
public ConcurrentHashMap getMap() {
return map;
}
public ConcurrentHashMap(Map extends K, ? extends V> m) {
this.sizeCtl = DEFAULT_CAPACITY;
putAll(m);
}
public ConcurrentHashMap(int initialCapacity) {
if (initialCapacity < 0)
throw new IllegalArgumentException();
// 最小容量如果大于最大容量的一半,则直接使用最大容量
// 最小容量如果小于于最大容量的一半,则使用计算出来的容量
int cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ? MAXIMUM_CAPACITY : tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1));
this.sizeCtl = cap;
}
可以看到如果带有容量初始化涉及到容量的计算问题
/**
* 计算容量,得到容量一定是刚好是大于 C 的 2^x 次幂
*/
private static final int tableSizeFor(int c) {
int n = c - 1;
// 对 n 四次位运算后得到自最高位以下全部变为 1,相当于 z^x - 1
n |= n >>> 1;
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
// n < 0 时容量是 1
// n > 0, n 大于等于最大容量的时候,使用最大容量
// n < MAXIMUM_CAPACITY, 返回 n + 1, n + 1 是一定是 2^x
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
put 添加元素
public V put(K key, V value) {
return putVal(key, value, false);
}
put 方法调用 putVal 方法, putVal()方法的总体思路为:
- hash数组是否为空,为空则先调用initTable()方法进行初始化
- 如果hash数组已经初始化了,则根据hash值找到对应的数组下标,如果对应节点为空,通过cas方式直接插入
- 如果数组已经扩容,则进行数据迁移
- 如果数组该位置已经有值了,则需要对该节点加锁并进行数据插入操作。此时如果该节点是链表结构,则遍历链表,插入数据;如果如果该节点是红黑树结构,则调用红黑树的插值方法插入新值
- 针对链表结构,如果插入新元素后,hash数组长度超过阈值,则需要调用treeifyBin()方法进行扩容或者是将链表转换为红黑树
- 对哈希表的元素进行计数处理
final V putVal(K key, V value, boolean onlyIfAbsent) {
if (key == null || value == null)
throw new NullPointerException();
// 对 key 的 hash 值重新计算
int hash = spread(key.hashCode());
int binCount = 0;
for (Node[] tab = table; ;) {
Node f;
int n, i, fh;
// 如果数组是 null 或者大小是 0 则初始化
if (tab == null || (n = tab.length) == 0){
// 初始化数组
tab = initTable();
}else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
// i = key 的 hash 值对数组长度取余
// 找到 key 对应值赋值给 f, 如果对应 value 是 null,则基于 CAS 思想添加
if (casTabAt(tab, i, null, new Node(hash, key, value, null)))
break;
} else if ((fh = f.hash) == MOVED){
// f 不为 null,且 hash 值是 -1 表示当前节点正在迁移,然后进入自旋等待,直到所有数据迁移完成
tab = helpTransfer(tab, f);
}else {
V oldVal = null;
synchronized (f) {
// 对象还是是同一个对象时
if (tabAt(tab, i) == f) {
// f 的 hash 值大于 0
if (fh >= 0) {
binCount = 1;
for (Node e = f; ; ++binCount) {
K ek;
// 找到,如果不要 CAS ,直接修改值
if (e.hash == hash && ((ek = e.key) == key || (ek != null && key.equals(ek)))) {
oldVal = e.val;
if (!onlyIfAbsent)
e.val = value;
break;
}
// 循环到最后,不存在形同的 key 则添加到链表最后
Node pred = e;
if ((e = e.next) == null) {
pred.next = new Node(hash, key, value, null);
break;
}
}
} else if (f instanceof TreeBin) {
// 添加到红黑树中
Node p;
binCount = 2;
if ((p = ((TreeBin) f).putTreeVal(hash, key, value)) != null) {
oldVal = p.val;
if (!onlyIfAbsent)
p.val = value;
}
}
}
}
// binCount != 0 说明上面在做链表或者红黑树操作
if (binCount != 0) {
// 当前节点大于等于 8 则链表转成红黑树
if (binCount >= TREEIFY_THRESHOLD)
treeifyBin(tab, i);
// 如果是替换则返回原值
if (oldVal != null)
return oldVal;
break;
}
}
}
addCount(1L, binCount);
return null;
}
initTable 初始化 ConcurrentHashMap 数组 table
/**
* 初始化数组
*/
private final Node[] initTable() {
Node[] tab;
int sc;
while ((tab = table) == null || tab.length == 0) {
// 表初始化和大小调整控制符为负数,表示抢锁失败,其他线程正在执行初始化任务,
// 暂停当前正在执行的线程对象,并执行其他线程。
if ((sc = sizeCtl) < 0)
Thread.yield();
// 基于 CAS 思想,如果底层数组偏移量 SIZECTL 和 sc 值一致则将 -1 赋值给 sizeCtl,成功则进行初始化操作
else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
try {
if ((tab = table) == null || tab.length == 0) {
// 赋值初始化默认大小,16
int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
// new 一个数组
@SuppressWarnings("unchecked")
Node[] nt = (Node[]) new Node, ?>[n];
// 将这个数组赋值给 table,table 是 volatile 的
table = tab = nt;
// 如果 n 为 16 的话,那么这里 sc = 12
// 其实就是 0.75 * n
sc = n - (n >>> 2);
}
} finally {
// 初始化之后 sizeCtl = 12
sizeCtl = sc;
}
break;
}
}
return tab;
}
spread()方法
// key hash 值计算
// 将散列的较高位散布(XOR)较低,也将最高位强制为0。由于该表使用2的幂次掩码,
// 所以仅在当前掩码上方的位中变化的哈希集将始终发生冲突。 (众所周知的示例是在小表中包含连续整数的Float键集。)
// 因此,我们应用了一种变换,将向下传播较高位的影响。 在速度,实用性和位扩展质量之间需要权衡。
// 由于许多常见的哈希集已经合理分布(因此无法从扩展中受益),并且由于我们使用树来处理容器中的大量冲突集,
// 因此我们仅以最便宜的方式对一些移位后的位进行XOR,以减少系统损失, 以及合并最高位的影响,
// 否则由于表范围的限制,这些位将永远不会在索引计算中使用。
static final int spread(int h) {
// 1、h 向右无符号右移 16 位,将高位 16 位置位 0
// 2、第一步得到的结果和 h 进行异或操作,实际上是 h 的高位 16 位和低位 16 位进行异或操作
// 3、和 HASH_BITS 进行 与 操作, 01111111111111111111111111111111
return (h ^ (h >>> 16)) & HASH_BITS;
}
helpTransfer 协助数据迁移
当前节点如果是 ForwardingNode 子类表示该节点已经迁移,当前 ConcurrentHashMap 正在进行数据迁移 ,ForwardingNode 子类 hash 值是 -1 和成员变量 MOVED 相等
/**
* 如果正在调整大小,协助扩容
*/
final Node[] helpTransfer(Node[] tab, Node f) {
Node[] nextTab;
int sc;
// 数组不是 null,且 f 是ForwardingNode 子类,且 next 是数组
if (tab != null && (f instanceof ForwardingNode) && (nextTab = ((ForwardingNode) f).nextTable) != null) {
// 返回扩容校验标识
int rs = resizeStamp(tab.length);
// sizeCtl 如果处于扩容状态的话
while (nextTab == nextTable && table == tab && (sc = sizeCtl) < 0) {
// 如果 sc 的低 16 位不等于 标识符(校验异常 sizeCtl 变化了)
// 如果 sc == 标识符 + 1 (扩容结束了,不再有线程进行扩容)(默认第一个线程设置 sc ==rs 左移 16 位 + 2,当第一个线程结束扩容了,就会将 sc 减一。这个时候,sc 就等于 rs + 1)
// 如果 sc == 标识符 + 65535(帮助线程数已经达到最大)
// 如果 nextTable == null(结束扩容了)
// 如果 transferIndex <= 0 (所有数据都已经迁移完成)
// 结束循环
if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 || sc == rs + MAX_RESIZERS || transferIndex <= 0)
break;
// 抢到迁移任务则 sc + 1
if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) {
transfer(tab, nextTab);
break;
}
}
return nextTab;
}
return table;
}
treeifyBin 链表转红黑树
treeifyBin()方法用于对数组进行扩容或者是将链表结构转换为红黑树,当一个节点的元素个数大于阈值(默认值8)时,如果此时数组长度小于MIN_TREEIFY_CAPACITY(默认值64),则对数组进行扩容,否则将该节点转换为红黑树:
/**
* 链表转成红黑树
*
* tab 是 ConcurrentHashMap 的 table
* index 当前操作节点索引
*/
private final void treeifyBin(Node[] tab, int index) {
Node b;
int n, sc;
if (tab != null) {
// 数组长度小于 64 则进行扩容,并不是转成红黑树
if ((n = tab.length) < MIN_TREEIFY_CAPACITY)
tryPresize(n << 1);
else if ((b = tabAt(tab, index)) != null && b.hash >= 0) {
// 当前节点不是 null,且 hash 值大于等于 0 则表示当前节点没有再迁移
synchronized (b) {
// 乐观锁
if (tabAt(tab, index) == b) {
TreeNode hd = null, tl = null;
for (Node e = b; e != null; e = e.next) {
// 以链表形式连接起来
TreeNode p = new TreeNode(e.hash, e.key, e.val, null, null);
if ((p.prev = tl) == null)
hd = p;
else
tl.next = p;
tl = p;
}
// 转成红黑树完成之后重新放入 table 中,放入表里面的是红黑树的跟节点
setTabAt(tab, index, new TreeBin(hd));
}
}
}
}
}
tryPresize 尝试扩容
/**
* 尝试扩容
*/
private final void tryPresize(int size) {
// c:size 的 1.5 倍,再加 1,再往上取最近的 2 的 n 次方。
int c = (size >= (MAXIMUM_CAPACITY >>> 1)) ? MAXIMUM_CAPACITY : tableSizeFor(size + (size >>> 1) + 1);
int sc;
// sizeCtl 大于等于 0 表示还没其他线程进行扩容
while ((sc = sizeCtl) >= 0) {
Node[] tab = table;
int n;
// tab 是 null 或者数组长度是 0 表示是空,需要初始化
if (tab == null || (n = tab.length) == 0) {
n = (sc > c) ? sc : c;
if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
try {
if (table == tab) {
@SuppressWarnings("unchecked")
Node[] nt = (Node[]) new Node, ?>[n];
table = nt;
sc = n - (n >>> 2);
}
} finally {
// sizeCtl = 0.75 * n
sizeCtl = sc;
}
}
} else if (c <= sc || n >= MAXIMUM_CAPACITY)
// sc >= c 或者大于等于最大容量 则不进行扩容或者无法扩容了
break;
else if (tab == table) {
// 计算出来一个标识
int rs = resizeStamp(n);
if (sc < 0) {
Node[] nt;
// 如果 sc 的低 16 位不等于 标识符(校验异常 sizeCtl 变化了)
// 如果 sc == 标识符 + 1 (扩容结束了,不再有线程进行扩容)(默认第一个线程设置 sc ==rs 左移 16 位 + 2
// ,当第一个线程结束扩容了,就会将 sc 减一。这个时候,sc 就等于 rs + 1)
// 如果 sc == 标识符 + 65535(帮助线程数已经达到最大)
// 如果 nextTable == null(结束扩容了)
// 如果 transferIndex <= 0 (所有数据都已经迁移完成)
// 结束循环
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))
// 发起扩容, rs 左移十六位 + 2
transfer(tab, null);
}
}
}
transfer 数据迁移
在helpTransfer()、tryPresize()中都调用了transfer()方法。顾名思义,其核心功能就是执行数据迁移,即将数据从扩容前的数组迁移到扩容后的数组中。
/**
* 扩容并迁移核心部分
*/
private final void transfer(Node[] tab, Node[] nextTab) {
int n = tab.length, stride;
// 如果是多核, 数组长度无符号右移 3 位 除 cpu 核心数量得到结果如果小 16,设置步幅为 16
// stride 可以理解为”步长“,有 n 个位置是需要进行迁移的,
// 将这 n 个任务分为多个任务包,每个任务包有 stride 个任务
if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)
stride = MIN_TRANSFER_STRIDE;
// nextTab 是 null,表示最开始迁移(拆分子任务迁移 nextTab != null)
if (nextTab == null) {
try {
// new 一个 n 的 2 倍大小的数组,然后赋值到 nextTab
@SuppressWarnings("unchecked")
Node[] nt = (Node[]) new Node, ?>[n << 1];
nextTab = nt;
} catch (Throwable ex) {
sizeCtl = Integer.MAX_VALUE;
return;
}
// 将扩容后的数组赋值到 nextTable
nextTable = nextTab;
// 最新数组大小,用于控制迁移的位置
transferIndex = n;
}
int nextn = nextTab.length;
// ForwardingNode 翻译过来就是正在被迁移的 Node
// 这个构造方法会生成一个Node,key、value 和 next 都为 null,关键是 hash 为 MOVED
// 后面我们会看到,原数组中位置 i 处的节点完成迁移工作后,
// 就会将位置 i 处设置为这个 ForwardingNode,用来告诉其他线程该位置已经处理过了
// 所以它其实相当于是一个标志。
ForwardingNode fwd = new ForwardingNode(nextTab);
boolean advance = true;
boolean finishing = false;
for (int i = 0, bound = 0; ; ) {
Node f;
int fh;
while (advance) {
int nextIndex, nextBound;
if (--i >= bound || finishing){
// i 大于 bound 或者结束标识设置为 ture 表示拆分完成
advance = false;
}else if ((nextIndex = transferIndex) <= 0) {
// 将 transferIndex 值赋给 nextIndex
// 这里 transferIndex 一旦小于等于 0,说明原数组的所有位置都有相应的线程去处理了
i = -1;
advance = false;
} else if (U.compareAndSwapInt(this, TRANSFERINDEX, nextIndex, nextBound = (nextIndex > stride ? nextIndex - stride : 0))) {
// 设置需要拆分索引 TRANSFERINDEX 成功
// 看括号中的代码,nextBound 是这次迁移任务的边界,注意,是从后往前, 其实位置是 transferIndex - stride 相当于子任务拆分是从后向前
// 所以 bound 可以看做是数据迁移边界
bound = nextBound;
i = nextIndex - 1;
advance = false;
}
}
if (i < 0 || i >= n || i + n >= nextn) {
int sc;
if (finishing) {
// 如果数据迁移结束,则将 nextTable 设置为null
nextTable = null;
// 重新赋值 table
table = nextTab;
// sizeCtl 设置为 n 的 3/4 - 1
sizeCtl = (n << 1) - (n >>> 1);
return;
}
// 之前我们说过,sizeCtl 在迁移前会设置为 (rs << RESIZE_STAMP_SHIFT) + 2
// 然后,每有一个线程参与迁移就会将 sizeCtl 加 1,
// 这里使用 CAS 操作对 sizeCtl 进行减 1,代表做完了属于自己的任务
if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) {
// 设置 SIZECTL 成功
if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT)
return;
// 都设置为 true
finishing = advance = true;
i = n;
}
// 如果位置 i 处是空的,没有任何节点,那么放入刚刚初始化的 ForwardingNode ”空节点“
} else if ((f = tabAt(tab, i)) == null){
advance = casTabAt(tab, i, null, fwd);
// 该位置处是一个 ForwardingNode,代表该位置已经迁移过了
}else if ((fh = f.hash) == MOVED){
advance = true;
}else {
synchronized (f) {
// 乐观锁判断
if (tabAt(tab, i) == f) {
Node ln, hn;
// 头结点的 hash 大于 0,说明是链表的 Node 节点
if (fh >= 0) {
// 下面这一块和 Java7 中的 ConcurrentHashMap 迁移是差不多的,
// 需要将链表一分为二,
// 找到原链表中的 lastRun,然后 lastRun 及其之后的节点是一起进行迁移的
// lastRun 之前的节点需要进行克隆,然后分到两个链表中
int runBit = fh & n;
Node lastRun = f;
// 遍历链表
for (Node 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;
}
// 链表会被分为两部分,与操作是 0 的位置不变,剩下的会变为当前位置 + n
for (Node 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(ph, pk, pv, ln);
else
hn = new Node(ph, pk, pv, hn);
}
// 其中的一个链表放在新数组的位置 i
setTabAt(nextTab, i, ln);
// 另一个链表放在新数组的位置 i+n
setTabAt(nextTab, i + n, hn);
// 将原数组该位置处设置为 fwd,代表该位置已经处理完毕,
// 其他线程一旦看到该位置的 hash 值为 MOVED,就不会进行迁移了
setTabAt(tab, i, fwd);
// advance 设置为 true,代表该位置已经迁移完毕,进行下一节点迁移
advance = true;
} else if (f instanceof TreeBin) {
// 红黑树
TreeBin t = (TreeBin) f;
TreeNode lo = null, loTail = null;
TreeNode hi = null, hiTail = null;
int lc = 0, hc = 0;
// 红黑树迁移按照排序顺序,从最小开始迁移
for (Node e = t.first; e != null; e = e.next) {
int h = e.hash;
TreeNode p = new TreeNode(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;
}
}
// 节点数量小于等于 6 则转成链表
// 节点数量大于 6,如果红黑树被分成两半则这里将 lo 设置为红黑树, 如果没有被分成凉拌则相当于所有元素位置不变
ln = (lc <= UNTREEIFY_THRESHOLD) ? untreeify(lo) : (hc != 0) ? new TreeBin(lo) : t;
hn = (hc <= UNTREEIFY_THRESHOLD) ? untreeify(hi) : (lc != 0) ? new TreeBin(hi) : t;
// 将红黑树第一段放到新数组中
setTabAt(nextTab, i, ln);
// 将红黑树第二段放到新数组中
setTabAt(nextTab, i + n, hn);
// 将原数组该位置处设置为 fwd,代表该位置已经处理完毕,
// 其他线程一旦看到该位置的 hash 值为 MOVED,就不会进行迁移了
setTabAt(tab, i, fwd);
// advance 设置为 true,代表该位置已经迁移完毕,进行下一节点迁移
advance = true;
}
}
}
}
}
}
addCount 计数器盒子
addCount 方法做了 2 件事情:
- 对 table 的长度加一。无论是通过修改 baseCount,还是通过使用 CounterCell。当 CounterCell 被初始化了,就优先使用他,不再使用 baseCount。
- 检查 table 是否需要扩容,或者是否正在扩容。如果需要扩容,就调用扩容方法,如果正在扩容,就帮助其扩容。
有几个要点注意:
- 第一次调用扩容方法前,sizeCtl 的低 16 位是加 2 的,不是加一。所以 sc == rs + 1 的判断是表示是否完成任务了。因为完成扩容后,sizeCtl == rs + 1。
- 扩容线程最大数量是 65535,是由于低 16 位的位数限制。
- 这里也是可以帮助扩容的,类似 helpTransfer 方法。
该方法主要逻辑:x 参数表示的此次需要对表中元素的个数加几。check 参数表示是否需要进行扩容检查,大于等于0 需要进行检查,而我们的 putVal 方法的 binCount 参数最小也是 0 ,因此,每次添加元素都会进行检查。(除非是覆盖操作)
- 判断计数盒子属性是否是空,如果是空,就尝试修改 baseCount 变量,对该变量进行加 X。
- 如果计数盒子不是空,或者修改 baseCount 变量失败了,则放弃对 baseCount 进行操作。
- 如果计数盒子是 null 或者计数盒子的 length 是 0,或者随机取一个位置取于数组长度是 null,那么就对刚刚的元素进行 CAS 赋值。
- 如果赋值失败,或者满足上面的条件,则调用 fullAddCount 方法重新死循环插入。
- 这里如果操作 baseCount 失败了(或者计数盒子不是 Null),且对计数盒子赋值成功,那么就检查 check 变量,如果该变量小于等于 1. 直接结束。否则,计算一下 count 变量。
- 如果 check 大于等于 0 ,说明需要对是否扩容进行检查。
- 如果 map 的 size 大于 sizeCtl(扩容阈值),且 table 的长度小于 1 << 30,那么就进行扩容。
- 根据 length 得到一个标识符,然后,判断 sizeCtl 状态,如果小于 0 ,说明要么在初始化,要么在扩容。
- 如果正在扩容,那么就校验一下数据是否变化了(具体可以看上面代码的注释)。如果检验数据不通过,break。
- 如果校验数据通过了,那么将 sizeCtl 加一,表示多了一个线程帮助扩容。然后进行扩容。
- 如果没有在扩容,但是需要扩容。那么就将 sizeCtl 更新,赋值为标识符左移 16 位 —— 一个负数。然后加 2。 表示,已经有一个线程开始扩容了。然后进行扩容。然后再次更新 count,看看是否还需要扩容。
/**
* 添加计数,如果表太小且尚未调整大小,则启动扩容。
* 如果已经调整大小,则在工作可用时帮助执行转移。
* 转移后重新检查占用率,以查看是否由于调整大小落后于其他调整而需要重新调整大小。
*
* 从 putVal 传入的参数是 1, binCount,binCount 默认是0,只有 hash 冲突了才会大于 1.且他的大小是链表的长度(如果不是红黑数结构的话)。
*/
private final void addCount(long x, int check) {
CounterCell[] as;
long b, s;
// 如果CounterCell数组中存在值,则说明有 更新值没有存储到baseCount 中
// 并且CAS 中存储的baseCount值 不一样,需要把差量数据全量插入
// 如果相同则 更新 baseCount 的值 = baseCount + x
if ((as = counterCells) != null || !U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) {
CounterCell a;
long v;
int m;
boolean uncontended = true;
// 高并发下 CAS 失败会执行 fullAddCount 方法
if (as == null || (m = as.length - 1) < 0 || (a = as[ThreadLocalRandom.getProbe() & m]) == null || !(uncontended = U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) {
fullAddCount(x, uncontended);
return;
}
// 小于等于 1 表示没有新增元素只是替换,所以计数不需要增加
if (check <= 1)
return;
// 大于 1,且存在并发
s = sumCount();
}
// 如果需要检查,检查是否需要扩容,在 putVal 方法调用时,默认就是要检查的。
if (check >= 0) {
Node[] tab, nt;
int n, sc;
// 如果map.size() 大于 sizeCtl(达到扩容阈值需要扩容) 且
// table 不是空;且 table 的长度小于 1 << 30。(可以扩容)
while (s >= (long) (sc = sizeCtl) && (tab = table) != null && (n = tab.length) < MAXIMUM_CAPACITY) {
// 根据 length 得到一个扩容标识
int rs = resizeStamp(n);
// 如果正在扩容
if (sc < 0) {
// 如果 sc 的低 16 位不等于 标识符(校验异常 sizeCtl 变化了)
// 如果 sc == 标识符 + 1 (扩容结束了,不再有线程进行扩容)(默认第一个线程设置 sc ==rs 左移 16 位 + 2,当第一个线程结束扩容了,就会将 sc 减一。这个时候,sc 就等于 rs + 1)
// 如果 sc == 标识符 + 65535(帮助线程数已经达到最大)
// 如果 nextTable == null(结束扩容了)
// 如果 transferIndex <= 0 (所有数据都已经迁移完成)
// 结束循环
if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 || sc == rs + MAX_RESIZERS || (nt = nextTable) == null || transferIndex <= 0)
break;
// 如果可以帮助扩容,那么将 sc 加 1. 表示多了一个线程在帮助扩容
if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))
transfer(tab, nt);
} else if (U.compareAndSwapInt(this, SIZECTL, sc, (rs << RESIZE_STAMP_SHIFT) + 2)){
// 如果不在扩容,将 sc 更新:标识符左移 16 位 然后 + 2. 也就是变成一个负数。高 16 位是标识符,低 16 位初始是 2. 开始扩容
transfer(tab, null);
}
s = sumCount();
}
}
}
fullAddCount
注解@sun.misc.Contended用于解决伪共享问题。所谓伪共享,即是在同一缓存行(CPU缓存的基本单位)中存储了多个变量,当其中一个变量被修改时,就会影响到同一缓存行内的其他变量,导致它们也要跟着被标记为失效,其他变量的缓存命中率将会受到影响。解决伪共享问题的方法一般是对该变量填充一些无意义的占位数据,从而使它独享一个缓存行。
ConcurrentHashMap的计数设计与LongAdder类似。在一个低并发的情况下,就只是简单地使用CAS操作来对baseCount进行更新,但只要这个CAS操作失败一次,就代表有多个线程正在竞争,那么就转而使用CounterCell数组进行计数,数组内的每个ConuterCell都是一个独立的计数单元。
每个线程都会通过ThreadLocalRandom.getProbe() & m寻址找到属于它的CounterCell,然后进行计数。ThreadLocalRandom是一个线程私有的伪随机数生成器,每个线程的probe都是不同的(这点基于ThreadLocalRandom的内部实现,它在内部维护了一个probeGenerator,这是一个类型为AtomicInteger的静态常量,每当初始化一个ThreadLocalRandom时probeGenerator都会先自增一个常量然后返回的整数即为当前线程的probe,probe变量被维护在Thread对象中),可以认为每个线程的probe就是它在CounterCell数组中的hash code。
这种方法将竞争数据按照线程的粒度进行分离,相比所有竞争线程对一个共享变量使用CAS不断尝试在性能上要效率多了,这也是为什么在高并发环境下LongAdder要优于AtomicInteger的原因。
fullAddCount()函数根据当前线程的probe寻找对应的CounterCell进行计数,如果CounterCell数组未被初始化,则初始化CounterCell数组和CounterCell。该函数的实现与Striped64类(LongAdder的父类)的longAccumulate()函数是一样的,把CounterCell数组当成一个散列表,每个线程的probe就是hash code,散列函数也仅仅是简单的(n - 1) & probe。
CounterCell数组的大小永远是一个2的n次方,初始容量为2,每次扩容的新容量都是之前容量乘以二,处于性能考虑,它的最大容量上限是机器的CPU数量。
所以说CounterCell数组的碰撞冲突是很严重的,因为它的bucket基数太小了。而发生碰撞就代表着一个CounterCell会被多个线程竞争,为了解决这个问题,Doug Lea使用无限循环加上CAS来模拟出一个自旋锁来保证线程安全,自旋锁的实现基于一个被volatile修饰的整数变量,该变量只会有两种状态:0和1,当它被设置为0时表示没有加锁,当它被设置为1时表示已被其他线程加锁。这个自旋锁用于保护初始化CounterCell、初始化CounterCell数组以及对CounterCell数组进行扩容时的安全。
CounterCell更新计数是依赖于CAS的,每次循环都会尝试通过CAS进行更新,如果成功就退出无限循环,否则就调用ThreadLocalRandom.advanceProbe()函数为当前线程更新probe,然后重新开始循环,以期望下一次寻址到的CounterCell没有被其他线程竞争。
如果连着两次CAS更新都没有成功,那么会对CounterCell数组进行一次扩容,这个扩容操作只会在当前循环中触发一次,而且只能在容量小于上限时触发。
fullAddCount()函数的主要流程如下:
- 首先检查当前线程有没有初始化过ThreadLocalRandom,如果没有则进行初始化。ThreadLocalRandom负责更新线程的probe,而probe又是在数组中进行寻址的关键。
- 检查CounterCell数组是否已经初始化,如果已初始化,那么就根据probe找到对应的CounterCell。
- 如果这个CounterCell等于null,需要先初始化CounterCell,通过把计数增量传入构造函数,所以初始化只要成功就说明更新计数已经完成了。初始化的过程需要获取自旋锁。
- 如果不为null,就按上文所说的逻辑对CounterCell实施更新计数。
- CounterCell数组未被初始化,尝试获取自旋锁,进行初始化。数组初始化的过程会附带初始化一个CounterCell来记录计数增量,所以只要初始化成功就表示更新计数完成。
- 如果自旋锁被其他线程占用,无法进行数组的初始化,只好通过CAS更新baseCount。
private final void fullAddCount(long x, boolean wasUncontended) {
int h;
// 当前线程的probe等于0,证明该线程的ThreadLocalRandom还未被初始化
// 以及当前线程是第一次进入该函数
if ((h = ThreadLocalRandom.getProbe()) == 0) {
// 第一次进来则进行初始化
ThreadLocalRandom.localInit();
// 重新获取探测值
h = ThreadLocalRandom.getProbe();
// 未竞争标志
wasUncontended = true;
}
// 冲突标志
boolean collide = false;
for (; ; ) {
CounterCell[] as;
CounterCell a;
int n;
long v;
// CounterCell数组已初始化
if ((as = counterCells) != null && (n = as.length) > 0) {
// 如果寻址到的Cell为空,那么创建一个新的Cell,说明当前线程第一次失败
if ((a = as[(n - 1) & h]) == null) {
// cellsBusy是一个只有0和1两个状态的volatile整数
// 它被当做一个自旋锁,0代表无锁,1代表加锁
if (cellsBusy == 0) { // Try to attach new Cell
// 将传入的x作为初始值创建一个新的CounterCell
CounterCell r = new CounterCell(x); // Optimistic create
// 加锁成功,声明Cell是否创建成功的标志
if (cellsBusy == 0 && U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {
boolean created = false;
try { // Recheck under lock
CounterCell[] rs;
int m, j;
// 再次检查CounterCell数组是否不为空
// 并且寻址到的Cell为空
// 其实就是乐观锁检查
if ((rs = counterCells) != null && (m = rs.length) > 0 && rs[j = (m - 1) & h] == null) {
// 将之前创建的新Cell放入数组
rs[j] = r;
created = true;
}
} finally {
// 释放锁
cellsBusy = 0;
}
// 创建成功调处循环
// 因为新Cell的初始值就是传入的增量,所以计数已经完毕了
if (created)
break;
// 如果未成功
// 代表as[(n - 1) & h]这个位置的Cell已经被其他线程设置
// 那么就从循环头重新开始
continue; // Slot is now non-empty
}
}
collide = false;
} else if (!wasUncontended) // CAS already known to fail
// as[(n - 1) & h]非空
// 在addCount()函数中通过CAS更新当前线程的Cell进行计数失败
// 会传入wasUncontended = false,代表已经有其他线程进行竞争
// 设置未竞争标志,之后会重新计算probe,然后重新执行循环
wasUncontended = true;
// 尝试进行计数,如果成功,那么就退出循环
else if (U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))
break;
// 尝试更新失败,检查counterCell数组是否已经扩容
// 或者容量达到最大值(CPU的数量)
else if (counterCells != as || n >= NCPU)
// 设置冲突标志,防止跳入下面的扩容分支
// 之后会重新计算probe
collide = false;
// 设置冲突标志,重新执行循环
// 如果下次循环执行到该分支,并且冲突标志仍然为true
// 那么会跳过该分支,到下一个分支进行扩容
else if (!collide)
collide = true;
// 尝试加锁,然后对counterCells数组进行扩容
else if (cellsBusy == 0 && U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {
try {
// 检查是否已被扩容
if (counterCells == as) {// Expand table unless stale
// 新数组容量为之前的1倍
CounterCell[] rs = new CounterCell[n << 1];
for (int i = 0; i < n; ++i)
rs[i] = as[i];
counterCells = rs;
}
} finally {
// 释放锁
cellsBusy = 0;
}
collide = false;
// 重新执行循环
continue; // Retry with expanded table
}
// 为当前线程重新计算probe
h = ThreadLocalRandom.advanceProbe(h);
} else if (cellsBusy == 0 && counterCells == as && U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {
// CounterCell数组未初始化,尝试获取自旋锁,然后进行初始化
boolean init = false;
try { // Initialize table
if (counterCells == as) {
// 初始化CounterCell数组,初始容量为2
CounterCell[] rs = new CounterCell[2];
rs[h & 1] = new CounterCell(x);
counterCells = rs;
init = true;
}
} finally {
cellsBusy = 0;
}
// 初始化CounterCell数组成功,退出循环
if (init)
break;
} else if (U.compareAndSwapLong(this, BASECOUNT, v = baseCount, v + x))
// 如果自旋锁被占用,则只好尝试更新baseCount,更新成功退出否则自旋
break;
}
}
这部分使用了内部类CounterCell
@sun.misc.Contended static final class CounterCell {
volatile long value;
CounterCell(long x) { value = x; }
}
ConcurrentHashMap 数据迁移过程中使用的 ForwardingNode 对象
/**
* Node 子类
*/
static final class ForwardingNode extends Node {
final Node[] nextTable;
// 将节点hashCode 设置为 -1,tab 赋值给 nextTable
ForwardingNode(Node[] tab) {
super(MOVED, null, null, null);
this.nextTable = tab;
}
Node find(int h, Object k) {
// loop to avoid arbitrarily deep recursion on forwarding nodes
outer:
for (Node[] tab = nextTable; ; ) {
Node e;
int n;
// 不存在返回 null
if (k == null || tab == null || (n = tab.length) == 0 || (e = tabAt(tab, (n - 1) & h)) == null)
return null;
// 循环找
for (; ; ) {
int eh;
K ek;
// 节点 hash 值相等,key 指向地址相同,且 key 非 null 且 hashCode 相等
if ((eh = e.hash) == h && ((ek = e.key) == k || (ek != null && k.equals(ek))))
return e;
// 如果 hash 值小于 0
if (eh < 0) {
// 如果 e 是 ForwardingNode 子类,则修改 tab 指向,重新循环
if (e instanceof ForwardingNode) {
tab = ((ForwardingNode) e).nextTable;
continue outer;
} else
// 递归查找
return e.find(h, k);
}
// 最终没找到,则返回 null
if ((e = e.next) == null)
return null;
}
}
}
}
ConcurrentHashMap 中红黑树
static final class TreeBin extends Node {
TreeNode root;
volatile TreeNode first;
volatile Thread waiter;
volatile int lockState;
// values for lockState
static final int WRITER = 1; // set while holding write lock
static final int WAITER = 2; // set when waiting for write lock
static final int READER = 4; // increment value for setting read lock
//
static int tieBreakOrder(Object a, Object b) {
int d;
if (a == null || b == null || (d = a.getClass().getName().compareTo(b.getClass().getName())) == 0)
d = (System.identityHashCode(a) <= System.identityHashCode(b) ? -1 : 1);
return d;
}
/**
* Creates bin with initial set of nodes headed by b.
*/
TreeBin(TreeNode b) {
super(TREEBIN, null, null, null);
// 链表头,变成红黑树之后,还维持这之前链表的关系
this.first = b;
TreeNode r = null;
for (TreeNode x = b, next; x != null; x = next) {
next = (TreeNode) 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 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 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);
break;
}
}
}
}
// 最后将根节点设置到 root
this.root = r;
assert checkInvariants(root);
}
// 获取用于树重组的写锁。
private final void lockRoot() {
if (!U.compareAndSwapInt(this, LOCKSTATE, 0, WRITER))
contendedLock(); // offload to separate method
}
/**
* 释放用于树重组的写锁。
*/
private final void unlockRoot() {
lockState = 0;
}
/**
* 可能阻止等待root用户锁定。
*/
private final void contendedLock() {
boolean waiting = false;
for (int s; ; ) {
if (((s = lockState) & ~WAITER) == 0) {
if (U.compareAndSwapInt(this, LOCKSTATE, s, WRITER)) {
if (waiting)
waiter = null;
return;
}
} else if ((s & WAITER) == 0) {
if (U.compareAndSwapInt(this, LOCKSTATE, s, s | WAITER)) {
waiting = true;
waiter = Thread.currentThread();
}
} else if (waiting)
LockSupport.park(this);
}
}
/**
* 返回匹配的节点;如果没有,则返回null。 尝试从根目录使用树比较进行搜索,但是在锁不可用时继续线性搜索。
*/
final Node find(int h, Object k) {
if (k != null) {
for (Node e = first; e != null; ) {
int s;
K ek;
// s = 0 或 4
if (((s = lockState) & (WAITER | WRITER)) != 0) {
// 线性查找,按照链表查询
if (e.hash == h && ((ek = e.key) == k || (ek != null && k.equals(ek))))
return e;
e = e.next;
} else if (U.compareAndSwapInt(this, LOCKSTATE, s, s + READER)) {
// 否则按照红黑树查找
TreeNode r, p;
try {
p = ((r = root) == null ? null : r.findTreeNode(h, k, null));
} finally {
Thread w;
if (U.getAndAddInt(this, LOCKSTATE, -READER) == (READER | WAITER) && (w = waiter) != null)
LockSupport.unpark(w);
}
return p;
}
}
}
return null;
}
/**
* 红黑树添加元素
*/
final TreeNode putTreeVal(int h, K k, V v) {
Class> kc = null;
boolean searched = false;
for (TreeNode p = root; ; ) {
int dir, ph;
K pk;
// 根节点是是 null,则插入节点就是根节点
if (p == null) {
first = root = new TreeNode(h, k, v, null, null);
break;
} else if ((ph = p.hash) > h)
// 当前节点hash 值大于插入节点 hash
dir = -1;
else if (ph < h)
// 当前节点hash 值小于插入节点 hash
dir = 1;
else if ((pk = p.key) == k || (pk != null && k.equals(pk)))
// hash 值相等,key 指向地址相等或者key 的 值相等返回 p 节点, 节点值变更不会引起红黑树失衡所以不需要调整,这里相当于找到节点返回外面进行赋值处理
return p;
else if ((kc == null && (kc = comparableClassFor(k)) == null) || (dir = compareComparables(kc, k, pk)) == 0) {
// hash 值相等个,key 不一致情况:kc 是 null, 且key 类是也 null 或者按照 key 的类 kc 进行 k 和 pk 的比较如果相等表示找到
if (!searched) {
// 如果这第一次搜索到
TreeNode q, ch;
searched = true;
// 当前节点左节点非 null 且 左子树找到对应节点 或者 右节点非 null 且右节点找到则返回 q
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;
}
// 左右子树都没找到比较k 和 pk
dir = tieBreakOrder(k, pk);
}
TreeNode xp = p;
// dir 小于等于 0,则左子树找,大于 0 右子树找直到找到叶子节点
if ((p = (dir <= 0) ? p.left : p.right) == null) {
TreeNode x, f = first;
// 将当前节点的父节点设置为 xp, 将当前节点设置为链表第一个元素,之前第一个元素设置为当前节点的下一个节点
first = x = new TreeNode(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;
}
/**
* 删除节点
* p 需要删除的节点
*/
final boolean removeTreeNode(TreeNode p) {
// 链表下一节点
TreeNode next = (TreeNode) p.next;
// 链表前一节点
TreeNode pred = p.prev;
// 删除节点是链表头,则将下一元素设置为链表头
TreeNode r, rl;
if (pred == null)
first = next;
else
// 不是链表头则修改前节点的的next 指向
pred.next = next;
// 下一节点非 null 则将下一节点的前节点指向前前节点
if (next != null)
next.prev = pred;
// 删除之后,如果链表空了,表示红黑树也空了,所以直接设置为 null
if (first == null) {
root = null;
return true;
}
// 空树,或者根节点右孩子为 null,或者右孩子的左孩子为 null 直接返回
if ((r = root) == null || r.right == null || (rl = r.left) == null || rl.left == null)
return true;
// 红黑树加锁
lockRoot();
try {
TreeNode replacement;
TreeNode pl = p.left;
TreeNode pr = p.right;
// 删除节点的左右孩子都在
if (pl != null && pr != null) {
TreeNode s = pr, sl;
// 寻找后继节点
while ((sl = s.left) != null)
s = sl;
// 这里开始是交换后继节点和删除节点
// 将后继节点的颜色设置删除节点颜色,将删除节点设置为后继节点颜色
boolean c = s.red;
s.red = p.red;
p.red = c; // swap colors
TreeNode sr = s.right;
TreeNode pp = p.parent;
// 后继节点是删除节点的右孩子
if (s == pr) { // p was s's direct parent
// 将 p 的右孩子设置为 p 的父节点,将后继节点的右孩子设置为 p
p.parent = s;
s.right = p;
} else {
// p 不是后继节点的直接父节点
TreeNode sp = s.parent;
// 后继节点的存在父节点
if ((p.parent = sp) != null) {
// 后继节点是左孩子则将 p 设置为左孩子
if (s == sp.left)
sp.left = p;
else
// 后继节点是右孩子则将 p 设置为右孩子
sp.right = p;
}
// p 节点右孩子非 null 则将拼接垫右孩子父节点设置为后继节点
if ((s.right = pr) != null)
pr.parent = s;
}
// 将 p 节点左孩子设置为 null,因为此时后继节点的左孩子一定是 null
p.left = null;
// 将后继节点右孩子设置为 p 节点右孩子,如果存在右孩子,则讲右孩子的父节点设置为 p
if ((p.right = sr) != null)
sr.parent = p;
// 将后继节点的左孩子设置为 p 节点左孩子,如果存在左孩子则将 p 节点原来左孩子的父节点设置为后继节点
if ((s.left = pl) != null)
pl.parent = s;
// 将 p 节点原父节点设置为后继节点的父节点,如果是 null 则将后继节点设置为红黑树的根
if ((s.parent = pp) == null)
r = s;
else if (p == pp.left)
// p 是左孩子,则将后继节点设置父 p 原父节点的左孩子
pp.left = s;
else
// p 是右孩子,则将后继节点设置父 p 原父节点的右孩子
pp.right = s;
// 后继节点不是叶子节点,右孩子非 null ,将其设置为替代节点,否则设置为 p
if (sr != null)
replacement = sr;
else
// 后继节点是叶子节点,这里 p 和后继节点已经交换完毕,所以 p 变成叶子节点可以直接删除
replacement = p;
} else if (pl != null)
// p 节点只有左孩子,则左孩子是替代节点
replacement = pl;
else if (pr != null)
// p 节点之后右孩子,则右孩子是替代节点
replacement = pr;
else
// 没有左右孩子,则 P 是叶子节点或者根节点,直接删除即可,所以 P 是替代节点
replacement = p;
// 替代节点不是删除节点
if (replacement != p) {
// 将替 p 的父节点设置为替代节点的父节点
TreeNode pp = replacement.parent = p.parent;
// p 的父节点是 null,则将替代节点设置为根借贷
// p 是左孩子则将替代节点设置为左孩子
// p 是右孩子则将替代节点设置为右孩子
if (pp == null)
r = replacement;
else if (p == pp.left)
pp.left = replacement;
else
pp.right = replacement;
// 将 p 的左孩子,右孩子和父节点设置为 null
p.left = p.right = p.parent = null;
}
// p 节点是红色节点则则直接删除,不影响黑节点数量
// p 节点是黑色节点需要删除后平衡
root = (p.red) ? r : balanceDeletion(r, replacement);
// p 本身是是替代节点,即是叶子节点或者根节点,将父节点指向自己的左孩子或者右孩子设置为 null
if (p == replacement) { // detach pointers
TreeNode pp;
if ((pp = p.parent) != null) {
if (p == pp.left)
pp.left = null;
else if (p == pp.right)
pp.right = null;
p.parent = null;
}
}
} finally {
// 释放红黑树的锁
unlockRoot();
}
assert checkInvariants(root);
return false;
}
// 左旋
static TreeNode rotateLeft(TreeNode root, TreeNode p) {
TreeNode r, pp, rl;
// 右旋右孩子不能为空
if (p != null && (r = p.right) != null) {
// 将 p 右孩子的左孩子设置为 p 的右孩子且赋值给 rl,如果 rl 非null,则将 rl 的父节点设置为 p 节点
if ((rl = p.right = r.left) != null)
rl.parent = p;
// 将 p 的父节点设置为,p 右孩子的父节点, 如果父节点不存在说明是根节点,则将 p 的右孩子颜色设置为黑色
if ((pp = r.parent = p.parent) == null)
// 将 p 的右孩子设置为根
(root = r).red = false;
else if (pp.left == p)
// p 节点是左孩子,则将 p 的右孩子设置为 p 的父节点的左孩子
pp.left = r;
else
// p 节点是右孩子,则将 p 的右孩子设置为 p 的父节点的右孩子
pp.right = r;
// 将 P 设置为右孩子的左节点
r.left = p;
// 将 p 的右孩子设置为 P 的父节点
p.parent = r;
}
return root;
}
// 右旋
static TreeNode rotateRight(TreeNode root, TreeNode p) {
TreeNode l, pp, lr;
// p 节点左孩子必须非空才能右旋
if (p != null && (l = p.left) != null) {
// 将 p 节点的左孩子的右孩子赋值给 p 的左节点
if ((lr = p.left = l.right) != null)
// 将 p 节点的左孩子的右孩子父节点设置为 p
lr.parent = p;
// 将 p 的父节点设置给 左孩
if ((pp = l.parent = p.parent) == null)
// p 是根节点的情况,将左孩子设置为根,颜色设置为黑色
(root = l).red = false;
else if (pp.right == p)
// p 是右孩子,则将 p 的左孩子设置为 p 的父节点的右孩子
pp.right = l;
else
// p 是左孩子,则将 p 的左孩子设置为 p 的父节点的左孩子
pp.left = l;
// 将 P 设置为左孩子的右孩子
l.right = p;
// 将 p 的左孩子变为 p 的父节点
p.parent = l;
}
return root;
}
/**
* 红黑树插入后平衡
* root 是红黑树的根
* x 是插入节点
*/
static TreeNode balanceInsertion(TreeNode root, TreeNode x) {
x.red = true;
for (TreeNode xp, xpp, xppl, xppr; ; ) {
// x 是根节点,颜色设置为黑色,跳出循环
if ((xp = x.parent) == null) {
x.red = false;
return x;
// 父节点是黑色,且父节点是根节点,不用处理
} else if (!xp.red || (xpp = xp.parent) == null)
return root;
// 父节点是左孩子
if (xp == (xppl = xpp.left)) {
// 父节点是左孩子,叔叔节点是红节点
if ((xppr = xpp.right) != null && xppr.red) {
// 将叔叔节点设置为黑色
xppr.red = false;
// 将父节点设置为黑色
xp.red = false;
// 将祖父节点设置为红色
xpp.red = true;
// 把祖父节点当做插入节点进行处理
x = xpp;
} else {
// 插入节点是右孩子,叔叔节点是黑节点或者不存在
if (x == xp.right) {
// 先对父节点左旋,然后把父节点当做插入节点处理
root = rotateLeft(root, x = xp);
// 获取插入节点的祖父节点
xpp = (xp = x.parent) == null ? null : xp.parent;
}
// 如果插入节点是左孩子,这里只是对祖父节点右旋,下次循环依然是对 插入节点进行处理
if (xp != null) {
// 将原节点设置黑色
xp.red = false;
// 祖父节点存在
if (xpp != null) {
// 将祖父节点设置为红色
xpp.red = true;
// 然后对祖父节点右旋
root = rotateRight(root, xpp);
}
}
}
} else {
// 父节点是右孩子,
// 叔叔节点存在,且是红节点
if (xppl != null && xppl.red) {
// 将叔叔节点变为黑节点
xppl.red = false;
// 父节点设置黑节点
xp.red = false;
// 富足节点变为哄节点
xpp.red = true;
// 将祖父节点变为下次循环需要调整的节点
x = xpp;
} else {
// 插入节点是左孩子,叔叔节点是黑节点或者不存在
if (x == xp.left) {
// 对父节点右旋,右旋之后插入节点变为父节点
root = rotateRight(root, x = xp);
// 这里 x 变为之前的父节点,赋值祖父节点,下次循环对父节点处理
xpp = (xp = x.parent) == null ? null : xp.parent;
}
// 插入节点的父节点非 null,下次循环记录处理对插入节点处理
if (xp != null) {
// 插入节点的父节点设置为黑节点
xp.red = false;
// 祖父节点存在
if (xpp != null) {
// 祖父节点变为红色
xpp.red = true;
// 对祖父节点进行左旋
root = rotateLeft(root, xpp);
}
}
}
}
}
}
/**
* 删除节点后红黑树自平衡
* root 红黑树的根
* x 删除节点的替代节点
*/
static TreeNode balanceDeletion(TreeNode root, TreeNode x) {
for (TreeNode xp, xpl, xpr; ; ) {
// 替代节点是根节点,则不需要处理
if (x == null || x == root)
return root;
// 替代节点不存在父节点也不是根节点,则是已经删除掉的节点不需要处理
else if ((xp = x.parent) == null) {
x.red = false;
return x;
} else if (x.red) {
// 替代节点是红节点,则只需要把替代节点变为黑节点即可
x.red = false;
return root;
} else if ((xpl = xp.left) == x) {
// 替代节点的父节点是左孩子,叔叔节点存在且是红节点
if ((xpr = xp.right) != null && xpr.red) {
// 将叔叔节点和父节点设置黑节点
// 然后对父节点进行左旋
xpr.red = false;
xp.red = true;
root = rotateLeft(root, xp);
// 将x 节点左旋之后的父节点的右孩子赋值 xpr
xpr = (xp = x.parent) == null ? null : xp.right;
}
// 兄弟节点不存在则将父节点当做插入节点进行处理
if (xpr == null)
x = xp;
else {
TreeNode sl = xpr.left, sr = xpr.right;
if ((sr == null || !sr.red) && (sl == null || !sl.red)) {
// 兄弟节点的左右孩子都存在且都是黑色节点
// 兄弟节点的左右节点存在一个且是黑色节点
// 兄弟节点不存在左右孩子
// 将兄弟节点的右孩子设置为红节点
// 将父节点当做插入节点当做插入节点处理
xpr.red = true;
x = xp;
} else {
// 兄弟节点存在左右孩子至少一个是红节点
if (sr == null || !sr.red) {
// 兄弟节点的右孩子不存在或者是黑色节点
// 兄弟节点左孩子非 null 则设置为黑节点
if (sl != null)
sl.red = false;
// 将兄弟节点右节点设置为红色节点
xpr.red = true;
// 对兄弟节点右旋
root = rotateRight(root, xpr);
// 旋转后小红心获取兄弟节点
xpr = (xp = x.parent) == null ? null : xp.right;
}
// 存在兄弟节点
if (xpr != null) {
// 想父节点颜色设置为兄弟节点颜色
xpr.red = (xp == null) ? false : xp.red;
// 兄弟节点右孩子存在则设置为黑色节点
if ((sr = xpr.right) != null)
sr.red = false;
}
// 父节点存在,将父节点设置黑色,然后对父节点左旋
if (xp != null) {
xp.red = false;
root = rotateLeft(root, xp);
}
x = root;
}
}
} else { // symmetric
// 替代节点是右孩子
// 叔叔节点存在且是红色节点
if (xpl != null && xpl.red) {
// 将叔叔节点设为黑节点
// 父节点设为红节点
// 对父节点右旋
xpl.red = false;
xp.red = true;
root = rotateRight(root, xp);
// 旋转之后将父节点的左孩子重新赋值
xpl = (xp = x.parent) == null ? null : xp.left;
}
if (xpl == null)
// 叔叔节点不存在则将父节点当错插入节点进行处理
x = xp;
else {
TreeNode sl = xpl.left, sr = xpl.right;
if ((sl == null || !sl.red) && (sr == null || !sr.red)) {
// 兄弟节点的左右孩子都存在且都是黑色节点
// 兄弟节点的左右节点存在一个且是黑色节点
// 兄弟节点不存在左右孩子
// 将兄弟节点的右孩子设置为红节点
// 将父节点当做插入节点当做插入节点处理
xpl.red = true;
x = xp;
} else {
// 兄弟节点左孩子不存在或者是黑色节点
if (sl == null || !sl.red) {
// 左孩子存在则将左孩子设置为黑节点
if (sr != null)
sr.red = false;
// 兄弟节点设置为红节点
xpl.red = true;
// 对兄弟节点左旋
root = rotateLeft(root, xpl);
xpl = (xp = x.parent) == null ? null : xp.left;
}
// 兄弟节点不存在
if (xpl != null) {
// 父节点不存在则设置为黑节点
xpl.red = (xp == null) ? false : xp.red;
// 兄弟节点左孩子存在设置为黑节点
if ((sl = xpl.left) != null)
sl.red = false;
}
// 父节点存在设置为黑色节点,对父节点右旋
if (xp != null) {
xp.red = false;
root = rotateRight(root, xp);
}
x = root;
}
}
}
}
}
/**
* Recursive invariant check
*/
static boolean checkInvariants(TreeNode t) {
TreeNode tp = t.parent, tl = t.left, tr = t.right,
tb = t.prev, tn = (TreeNode) t.next;
if (tb != null && tb.next != t)
return false;
if (tn != null && tn.prev != t)
return false;
if (tp != null && t != tp.left && t != tp.right)
return false;
if (tl != null && (tl.parent != t || tl.hash > t.hash))
return false;
if (tr != null && (tr.parent != t || tr.hash < t.hash))
return false;
if (t.red && tl != null && tl.red && tr != null && tr.red)
return false;
if (tl != null && !checkInvariants(tl))
return false;
if (tr != null && !checkInvariants(tr))
return false;
return true;
}
private static final sun.misc.Unsafe U;
private static final long LOCKSTATE;
static {
try {
U = sun.misc.Unsafe.getUnsafe();
Class> k = TreeBin.class;
LOCKSTATE = U.objectFieldOffset(k.getDeclaredField("lockState"));
} catch (Exception e) {
throw new Error(e);
}
}
}
参考资料:
https://www.sohu.com/a/254192521_355142
https://www.jianshu.com/p/749d1b8db066
https://www.jianshu.com/p/514e33ad6c35
https://www.jianshu.com/p/f93912fec48b
https://blog.csdn.net/sihai12345/article/details/79383766
https://blog.csdn.net/programmeryu/article/details/91606487
红黑树相关知识
https://www.jianshu.com/p/88881fdfcf4c