ConcurrentHashMap源码分析
看前须知
- 本文章所有内容是老师源码特训班的内容有兴趣的可以了解一波:https://space.bilibili.com/457326371?from=search&seid=882812707426049189
- 本章需要一些前置知识:LongAdder原理,HashMap原理(知道最好),红黑树,LockSupport,volatile,CAS,Unsafe
- 上述前置知识很多都可以在老师的公开课中找到
- 基于JDK8版本进行源码分析
JDK8与JDK7的存储结构总览
ConcurrentHashMap存储结构说明
- 总体来说与JDK8的HashMap存储结构并无太大区别,依然是:数组+链表+红黑树。
- 与JDK8HashMap结构不同的是多了FWD与TreeBin节点。
- FWD节点的作用:在JDK8中ConcurrentHashMap支持并发扩容而某个桶位是FWD节点就表示散列表正在扩容中,当前桶位的数据已经被迁移到了新表中,当有写线程看到FWD节点时会参与并发扩容,当读线程看到FWD节点会到新表中查询。
- TreeBin节点的作用:代理操作红黑树,里面维护了双向链表与红黑树结构。
重要常量说明
// 散列表最大值
private static final int MAXIMUM_CAPACITY = 1 << 30;
// 散列表默认值
private static final int DEFAULT_CAPACITY = 16;
// 默认并发级别,JDK7遗留下来的,在JDK8中基本没用到
private static final int DEFAULT_CONCURRENCY_LEVEL = 16;
// 负载因子,这里是final的,在HashMap中是可以修改的
private static final float LOAD_FACTOR = 0.75f;
// 链表转换为红黑树的阈值,链表长度达到8可能发生树化
static final int TREEIFY_THRESHOLD = 8;
// 红黑树转换为链表的阈值,当红黑树中的元素数量等于6的时候可能会把红黑树退化为链表
static final int UNTREEIFY_THRESHOLD = 6;
// 联合TREEIFY_THRESHOLD控制桶位是否发生树化,只有当table数组长度达到64并且某个桶为的链表长度大于等于8,才会真正树化
static final int MIN_TREEIFY_CAPACITY = 64;
// 线程迁移数据最小步长,控制线程迁移任务最小区间的一个值
private static final int MIN_TRANSFER_STRIDE = 16;
// 扩容相关,计算扩容时生成的一个标识戳
private static int RESIZE_STAMP_BITS = 16;
// 65535,表示并发扩容最多线程数
private static final int MAX_RESIZERS = (1 << (32 - RESIZE_STAMP_BITS)) - 1;
// 扩容相关
private static final int RESIZE_STAMP_SHIFT = 32 - RESIZE_STAMP_BITS;
// 当Node节点的hash值为-1时,表示当前节点是FWD节点
static final int MOVED = -1;
// 当Node节点的hash值为-2时,表示当前节点已经树化且当前节点为TreeBin对象,TreeBin对象代理操作红黑树
static final int TREEBIN = -2;
// 转换为二进制就是31位1,可以将一个负数通过位与运算后得到正数,简单来说就是通过这个值和位运算可以将一个值(负数)变为正数,但是转换后的正数不一定是原值的绝对值。
static final int HASH_BITS = 0x7fffffff;
// 当前电脑的逻辑核数量
static final int NCPU = Runtime.getRuntime().availableProcessors();
// Unsafe对象的引用
private static final sun.misc.Unsafe U;
// 表示sizeCtl属性在ConcurrentHashMap中内存地址的偏移量
private static final long SIZECTL;
// 表示TRANSFERINDEX属性在ConcurrentHashMap中内存地址的偏移量
private static final long TRANSFERINDEX;
// 表示BASECOUNT属性在ConcurrentHashMap中内存地址的偏移量
private static final long BASECOUNT;
// 表示CELLSBUSY属性在ConcurrentHashMap中内存地址的偏移量
private static final long CELLSBUSY;
// 表示CELLVALUE属性在CounterCell中内存地址的偏移量
private static final long CELLVALUE;
// 表示数组第一个元素的偏移地址
private static final long ABASE;
// table数组单元寻址使用,比如要访问table数组的第五个元素,正常是需要 ABASE+(5*scale),改成了位运算后 5<<ASHIFT即可
private static final int ASHIFT;
重要成员变量说明
// 散列表,长度一定是2的次方数
transient volatile Node<K,V>[] table;
// 扩容过程中,会将扩容中的新table 赋值给nextTable 保持引用,扩容结束后,这里会被设置为null
private transient volatile Node<K,V>[] nextTable;
// LongAdder中的baseCount,未发生竞争时或者当前LongAdder处于加锁状态中,增量累加到baseCount中
private transient volatile long baseCount;
// LongAdder中的cellsBusy,0表示当前LongAdder对象无锁状态,1表示加锁
private transient volatile int cellsBusy;
// LongAdder中的cells数组,当baseCount发生竞争后,会创建cells数组,线程会通过计算hash值,取到自己的cell,将增量累加到指定cell中。
private transient volatile CounterCell[] counterCells;
/*
* sizeCtl < 0
* 1. -1 表示当前table正在初始化(有线程在创建table数组),当前线程自旋等待
* 2. 表示当前map正在进行扩容,高16位表示扩容的标识戳,低16位表示当前参与并发扩容的线程数(1+nThread)
* sizeCtl = 0,表示创建table数组时 使用DEFAULT_CAPACITY为大小
* sizeCtl > 0
* 1. 如果table未初始化,表示初始化大小
* 2. 如果table已经初始化,表示下次扩容时的触发阈值
*/
private transient volatile int sizeCtl;
// 扩容过程中,记录当前进度,所有线程都需要从这个变量中分配区间任务,去执行自己的任务
private transient volatile int transferIndex;
重要的内部类解析(暂时只看属性不看方法)
Node
1.源代码
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
volatile V val;
volatile Node<K,V> next;
2.属性说明
- hash: key经过扰动运算后计算出来的hash值,用于散列表寻址。
- key: put时候的key
- val: put时候的value
- next: 用于形成链表
ForwardingNode(FWD)
1.源代码
static final class ForwardingNode<K,V> extends Node<K,V> {
final Node<K,V>[] nextTable;
}
2.属性说明
- nextTable: 引用新散列表
TreeNode
1.源代码
static final class TreeNode<K,V> extends Node<K,V> {
TreeNode<K,V> parent;
TreeNode<K,V> left;
TreeNode<K,V> right;
TreeNode<K,V> prev;
boolean red;
2.属性说明
- parent: 当前节点的父节点
- left: 左子节点
- right: 右子节点
- prev: 前一个节点
- red: boolean类型,表示节点的颜色,true是红,false是黑
- 其他属性: 继承自Node
TreeBin
1.源代码
static final class TreeBin<K,V> extends Node<K,V> {
TreeNode<K,V> root;
volatile TreeNode<K,V> first;
volatile Thread waiter;
volatile int lockState;
static final int WRITER = 1;
static final int WAITER = 2;
static final int READER = 4;
}
2.属性说明
- root: 红黑树的根节点
- first: 链表的头节点
- waiter: 等待者线程(当前lockState是读锁状态)
- lockState: 锁状态(写锁状态,读锁状态)
- WRITER: 常量1,表示写锁状态
- WAITER: 常量2,等待者状态(一定是个写线程在等待)
- READER: 常量4,表示写锁状态(每一个读线程都会+4)
ConcurrentHashMap的非核心方法解析(辅助方法)
spread方法
1.源代码
static final int spread(int h) {
return (h ^ (h >>> 16)) & HASH_BITS;
}
2.方法说明
Node类的hash值,并不是key的hash值,而是key的hash值通过扰动函数得来的,这个方法就是扰动函数。
tabAt方法
1.源代码
static final <K,V> Node<K,V> tabAt(Node<K,V>[] tab, int i) {
return (Node<K,V>)U.getObjectVolatile(tab, ((long)i << ASHIFT) + ABASE);
}
2.方法说明
使用Unsafe获取Node数组中,指定下标位置的元素(使用Unsafe的原因是为了性能)。
casTabAt方法
1.源代码
static final <K,V> boolean casTabAt(Node<K,V>[] tab, int i,
Node<K,V> c, Node<K,V> v) {
return U.compareAndSwapObject(tab, ((long)i << ASHIFT) + ABASE, c, v);
}
2.方法说明
通过CAS的方式,去向tab数组中指的位置去设置值。
setTabAt方法
1.源代码
static final <K,V> void setTabAt(Node<K,V>[] tab, int i, Node<K,V> v) {
U.putObjectVolatile(tab, ((long)i << ASHIFT) + ABASE, v);
}
2.方法说明
向指定数组的指定下标添加元素。
resizeStamp方法
1.源代码
static final int resizeStamp(int n) {
return Integer.numberOfLeadingZeros(n) | (1 << (RESIZE_STAMP_BITS - 1));
}
2.方法说明
计算扩容唯一标识戳,标识数组长度多少至多少的一个标识,线程来参与扩容的时候,一定要拿到这个标识戳,标识戳一致的情况下才能参与扩容。
构造方法
1.源代码
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;
}
2.方法说明
初始化sizeCtl表示容量。
initTable方法
1.源代码
private final Node<K,V>[] initTable() {
Node<K,V>[] tab; int sc;
// 判断散列表是否初始化
while ((tab = table) == null || tab.length == 0) {
// 如果散列表正在有其他线程初始化那么让出CPU或不断自旋
if ((sc = sizeCtl) < 0)
Thread.yield();
// 如果抢占锁成功那么进入初始化散列表逻辑
else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
try {
// 为了防止其他线程已经初始化过了散列表所以这里要再次判断
if ((tab = table) == null || tab.length == 0) {
// 如果sc>0就使用sc作为容量,如果小于等于0那么使用默认容量16
int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
@SuppressWarnings("unchecked")
Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
// 赋值给table
table = tab = nt;
// 下次扩容时的触发阈值,为当前容量的4/3
sc = n - (n >>> 2);
}
} finally {
// 释放锁,并且让sizeCtl表示触发扩容的阈值
sizeCtl = sc;
}
break;
}
}
return tab;
}
2.方法说明
初始化散列表,并设置sizeCtl表示触发扩容的阈值,使用CAS和状态变量来保证线程安全。
核心方法put解析
1.源代码
final V putVal(K key, V value, boolean onlyIfAbsent) {
// 基本判断保证参数没有问题
if (key == null || value == null) throw new NullPointerException();
// 使用扰动函数计算key的hash值
int hash = spread(key.hashCode());
// 初始化binCount为0
int binCount = 0;
// 自旋,并且把全局的table赋值给tab
for (Node<K,V>[] tab = table;;) {
/*
* 1. f——表示桶位的头节点
* 2. n——散列表数组的长度
* 3. fh——表示桶位头节点的hash值
* 4. i——对应的散列表的下标
*/
Node<K,V> f; int n, i, fh;
// 如果散列表没有初始化去初始化散列表(可能会有多个线程进入initTable方法不过没关系因为这个方法是线程安全的)
if (tab == null || (n = tab.length) == 0)
tab = initTable();
/*
* 1. 散列表已经初始化
* 2. (f = tabAt(tab, i = (n - 1) & hash))——使用tabAt获取对应桶位元素
* 3. 对应桶位元素如果是null
*/
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
// 使用casTabAt方法向对应桶位设置Node,并结束自旋
if (casTabAt(tab, i, null,
new Node<K,V>(hash, key, value, null)))
break;
}
/*
* 1. 散列表已经初始化
* 2. 对应桶位不是null
* 3. (fh = f.hash) == MOVED——对应桶位节点是FWD节点(只有FWD节点的hash才是-1)
*/
else if ((fh = f.hash) == MOVED)
// 参与并发扩容,并返回新的散列表
tab = helpTransfer(tab, f);
/*
* 1. 散列表已经初始化
* 2. 对应桶位不是null
* 3. 对应桶位不是FWD节点
* 4. 对应桶位是红黑树或链表
*/
else {
// 如果已经存在对应key,那么这个值就是原先的value
V oldVal = null;
// 使用synchronized锁住桶位节点保证线程安全,这也是一种分段锁思想的体现。
synchronized (f) {
// 使用tabAt方法再次获取散列表中的下标元素,并和桶位节点比较是否一致,这么做的原因是防止其他线程在锁住桶位节点前进行了修改造成散列表中对应下标的元素已经不是f了
if (tabAt(tab, i) == f) {
// 桶位节点hash值只有可能是:链表头节点(大于等于0),TreeBin节点(-2),FWD节点(-1)
// 如果是当前桶位是链表结构
if (fh >= 0) {
// binCount在链表情况下的含义: 当前插入key与所有元素的key都不一样的时候binCount表示链表长度,反过来表示冲突位置(binCount-1)
binCount = 1;
// 自旋,作用是遍历链表和计算binCount
for (Node<K,V> e = f;; ++binCount) {
// 链表当前元素的key
K ek;
// 如果链表中当前元素的key与指定key相等
if (e.hash == hash &&
((ek = e.key) == key ||
(ek != null && key.equals(ek)))) {
// 获取旧的value
oldVal = e.val;
// 如果需要替换
if (!onlyIfAbsent)
// 替换产生冲突的节点的value
e.val = value;
// 结束自旋
break;
}
// 获取当前节点
Node<K,V> pred = e;
// 如果当前节点已经是链表尾节点了
if ((e = e.next) == null) {
// 走到这里说明这个链表没有节点的key与插入key重复需要插入到链表的尾部
// 插入到链表尾部,并结束循环
pred.next = new Node<K,V>(hash, key,
value, null);
break;
}
}
}
// 如果当前桶位是红黑树结构
else if (f instanceof TreeBin) {
// 插入到红黑树中产生冲突的节点
Node<K,V> p;
// 设置binCount为2表示桶位已经树化
binCount = 2;
// 调用TreeBin的putTreeVal方法向红黑树中插入元素
// 如果没有产生冲突那么p是null,如果产生了冲突p才有值
if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
value)) != null) {
// 走到这里说明一定产生了冲突
// 获取原先的value
oldVal = p.val;
// 如果需要替换则替换
if (!onlyIfAbsent)
p.val = value;
}
}
}
}
// 如果插入到了链表或红黑树,需要检查是否需要树化
if (binCount != 0) {
// 这个条件成立说明需要树化(链表长度大于等于8)
if (binCount >= TREEIFY_THRESHOLD)
// 树化方法
treeifyBin(tab, i);
// 不需要树化,如果存在原先的value则返回
if (oldVal != null)
return oldVal;
break;
}
}
}
// 统计当前散列表一共有多少数据,判断是否达到扩容条件,达到则扩容.
addCount(1L, binCount);
// 走到这里说明没有产生过冲突返回null即可
return null;
}
2.该方法做的事情
- 检查散列表是否初始化如果没有去初始化散列表
- 如果检查到散列表正在扩容那么参与并发扩容
- 向各种结构插入数据
- 向空桶位插入数据
- 对应桶位对象是个FWD节点(参与并发扩容)
- 向桶位形成的链表插入数据
- 向桶位形成的红黑树(TreeBin)插入数据
- 根据条件是否要进行链表转红黑树
- 是否应该扩容散列表
3:变量说明
- hash: 经过扰动函数后,key的hash值
- binCount: 0表示寻址的桶位为null,2表示桶为已经树化
- tab: 引用table
- f: 桶为的头节点
- n: 散列表数组的长度
- i: 表示key通过寻址计算后,得到的桶为下标
- fh: 表示桶为头节点的hash值
- oldVal: 旧的value(如果有)
- binCount在链表情况下的含义: 当前插入key与所有元素的key都不一样的时候binCount表示链表长度,反过来表示冲突位置(binCount-1)
4:情况说明
- 散列表没有初始化
- 散列表已经初始化
- 对应桶位对象为null
- 对应桶位对象是个FWD节点
- 对应桶位形成链表
- 对应桶位形成红黑树(TreeBin)
- 链表达到转红黑树的条件
- 检查是否应该扩容
第一种情况:散列表没有初始化
源代码
if (tab == null || (n = tab.length) == 0)
tab = initTable();
代码分析
如果散列表没有初始化则调用initTable方法初始化散列表
第二种情况:对应桶位对象为null
源代码
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
if (casTabAt(tab, i, null,
new Node<K,V>(hash, key, value, null)))
break; // no lock when adding to empty bin
}
代码分析
获取对应桶位的节点,如果是null,则使用casTabAt()方法尝试去向散列表中添加Node对象,如果成功则退出自旋。
第三种情况:对应桶位对象是个FWD节点
源代码
else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
代码分析
如果对应桶位节点的hash值等于-1,说明这个节点是FWD节点,说明散列表正在扩容,当前线程有义务帮助扩容,通过helpTransfer来帮助扩容返回扩容后的散列表。
第四种情况:对应桶位形成链表
源代码
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;
}
}
}
代码分析
如果是链表,会有两种情况,第一种是这个链表已经存在指定key,第二种就是不存在,核心就是遍历链表如果链表中存在指定key那么就覆盖或什么都不干,如果不存在就插入到链表尾部,在这里binCount表示的是冲突位置(binCount-1)或链表长度,注意这段代码被sycnhronized锁住了不会有线程安全问题。
第五种情况:对应桶位形成红黑树
源代码
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;
}
}
代码分析
也有两种情况,与链表类似,这种情况下说明桶位节点是TreeBin节点,会调用TreeBin对象的putTreeVal向红黑树添加元素,如果返回值不是null,说明存在冲突把冲突节点返回了,最后根据onlyIfAbsent决定是否覆盖。
第六种情况:达到链表转红黑树的条件
源代码
if (binCount != 0) {
if (binCount >= TREEIFY_THRESHOLD)
treeifyBin(tab, i);
if (oldVal != null)
return oldVal;
break;
}
代码分析
只有对应桶位节点是链表或红黑树的时候才会大于0,如果 binCount > 8 说明链表长度一定大于8达到了链表转红黑树的条件调用treeifyBin()方法进行树化,否则直接返回oldVal。
第七种情况:检查是否应该扩容
源代码
addCount(1L, binCount);
代码分析
这个方法单独拿出来分析,这个方法的作用是,统计当前散列表一共有多少数据和判断是否达到扩容条件,达到则扩容。
核心方法addCount解析
1.源代码
private final void addCount(long x, int check) {
/*
* 1. as——Cells数组与LongAdder的含义一致
* 2. b——与LongAdder中的base一样
* 3. s——散列表元素数量
*/
CounterCell[] as; long b, s;
// 以下逻辑就是LongAdder的add方法逻辑
if ((as = counterCells) != null ||
!U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) {
CounterCell a; long v; int m;
boolean uncontended = true;
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. 如果是remove方法调用了这个方法check会是-1并不会参与并发扩容
* 2. 如果putVal时对应桶位为null并且设置成功那么check会是0
* 3. 如果putVal时链表头节点与指定key相同那么check会是1
*/
if (check <= 1)
return;
// 计算散列表中元素的数量
s = sumCount();
}
/*
* 那些线程应该参与到扩容中
* 1. 如果putVal时是红黑树情况
* 2. 如果是链表并且没有插入到链表的头节点上
*/
if (check >= 0) {
/*
* 1. tab——引用散列表
* 2. nt——nextTable
* 3. sc——扩容阈值
*/
Node<K,V>[] tab, nt; int n, sc;
/*
* 只有这几种情况都满足才会进行扩容
* 1. 如果散列表中的元素数量大于等于扩容阈值 或 散列表正在扩容
* 2. 散列表不是null(恒成立)
* 3. 散列表容量没有达到最大
*/
while (s >= (long)(sc = sizeCtl) && (tab = table) != null &&
(n = tab.length) < MAXIMUM_CAPACITY) {
// 计算扩容标识戳
int rs = resizeStamp(n);
// 散列表正在扩容
if (sc < 0) {
/*
* 判断的是不能参与扩容的情况
* 1. (sc >>> RESIZE_STAMP_SHIFT) != rs——取出sc的高16位是否等于当前线程的扩容戳,如果成立说明扩容标识戳不相等不能参与扩容。
* 2. sc == rs + 1——作者写错了,想写的是sc == (rs << 16) +1,如果成立表示本次扩容完毕了。
* 3. sc == rs + MAX_RESIZERS——作者写错了,想写的是sc == (rs << 16) + MAX_RESIZERS,如果成立表示参与扩容线程数量已经达到最大值(65534)。
* 4. (nt = nextTable) == null——本次扩容结束
* 5. transferIndex <= 0——全局的扩容进度已经小于等于0说明没有步长可以分配了
*/
if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||
transferIndex <= 0)
break;
// 可以扩容,使用CAS修改sizeCtl的低15位(低16位+1)
if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))
transfer(tab, nt);
}
// 散列表需要扩容但是还没有进行扩容,使用CAS修改sizeCtl(低16位+2)表示当前是第一个扩容线程
else if (U.compareAndSwapInt(this, SIZECTL, sc,
(rs << RESIZE_STAMP_SHIFT) + 2))
// 调用扩容方法
transfer(tab, null);
// 扩容完成后重新计算散列表元素数量
s = sumCount();
}
}
}
2.方法作用说明
- 统计当前散列表一共有多少数据
- 判断是否达到扩容条件,达到则扩容。
3.方法入参说明
- x: putVal方法调用为1
- check: 如果是2则表示桶位形成红黑树,如果不是2则代表链表长度或冲突下标位置
4:方法变量说明
- as: 与LongAdder中的Cell数组一样
- b: 与LongAdder中的base一样
- s: 散列表元素数量
- tab: 散列表引用
- nt: nextTable
- n: 散列表长度
- sc: 扩容阈值
- rs: 扩容标识戳
5:情况分析
- 计数器累加,如果是putVal方法则累加1
- 是否达到扩容条件
- 区分是扩容主线程还是辅助扩容线程
- 辅助扩容线程不能参与扩容
- 验证扩容戳
- 是否扩容完毕
- 扩容的线程达到最大值
- 扩容结束
- 能参与(当所有不满足情况都不符合)
第一种情况:计数器累加
源代码
CounterCell[] as; long b, s;
if ((as = counterCells) != null ||
!U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) {
CounterCell a; long v; int m;
boolean uncontended = true;
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;
}
if (check <= 1)
return;
s = sumCount();
}
代码分析
在putVal上下文中check的值分析,对应桶位为null并设置成功是0,链表头节点存在并且key相同那么是1,对应桶位是红黑树是2。
就是LongAdder的add方法,这段代码有两个作用
- 累加计数器
- 过滤一下那些线程应该去帮助其他线程进行扩容
有两种情况下会只累加而不会帮助其他线程扩容散列表
1.进入了fullAddCount方法因为这个方法逻辑较多耗时较长如果当前线程还要帮助其他线程扩容的话会耗时较久
2. 当前线程累加到指定cell成功
1. 如果是删除元素的情况(负数)
2. 如果putVal时对应桶位为null并且设置成功(0)
3. 如果putVal时链表头节点与指定key相同(1)
第二种情况:达到扩容条件
源代码
while (s >= (long)(sc = sizeCtl) && (tab = table) != null && (n = tab.length) < MAXIMUM_CAPACITY)
代码分析
- 第一个条件为true说明要么散列表正在扩容要么散列表元素数量大于扩容阈值
- 第二条件恒成立
- 第三个条件散列表长度必须小于最大容量
第三种情况:区分是扩容主线程还是辅助扩容线程
源代码
if (sc < 0) {
}else if (U.compareAndSwapInt(this, SIZECTL, sc,(rs << RESIZE_STAMP_SHIFT) + 2)){
}
代码说明
sc < 0 说明散列表正在扩容(当前线程是辅助扩容线程),否则就是说明当前线程可能是第一个来扩容的(扩容主线程)使用CAS修改sizeCtl如果修改成功就说明是第一个来扩容的,然后调用transfer(tab, null);
第四种情况:辅助线程不能参与扩容(标识戳错误)
源代码
(sc >>> RESIZE_STAMP_SHIFT) != rs
代码说明
取出sc的高16位是否等于当前线程的扩容戳,如果成立说明扩容标识戳不相等不能参与扩容。
第五种情况:辅助线程不能参与扩容(扩容完毕)
源代码
sc == rs + 1
代码分析
作者写错了,想写的是sc == (rs << 16) +1,如果成立表示本次扩容完毕了。
第六种情况:辅助线程不能参与扩容(扩容的线程达到最大值)
源代码
sc == rs + MAX_RESIZERS
代码分析
作者写错了,想写的是sc == (rs << 16) + MAX_RESIZERS,如果成立表示参与扩容线程数量已经达到最大值(65534)。
第七种情况:辅助线程不能参与扩容(扩容结束)
源代码
(nt = nextTable) == null
代码分析
本次扩容结束
第八种情况:能参与扩容
源代码
if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))
transfer(tab, nt);
代码解析
使用CAS更新sizeCtl,如果更新成功则可以去参与扩容了。
核心方法transfer解析
1.源代码
private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {
/*
* 1. n——扩容之前table数组的长度
* 2. stride——表示分配给线程任务的步长
*/
int n = tab.length, stride;
// 计算stride,把stride看成16就可以
if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)
stride = MIN_TRANSFER_STRIDE;
// 如果nextTable为null扩容线程初始化nextTable
if (nextTab == null) {
try {
// 创建新的散列表长度位旧散列表长度的2倍
@SuppressWarnings("unchecked")
Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n << 1];
// 赋值给nextTab
nextTab = nt;
} catch (Throwable ex) {
sizeCtl = Integer.MAX_VALUE;
return;
}
// 更新给全局的nextTable
nextTable = nextTab;
// 设置全局散列表迁移进度为n
transferIndex = n;
}
// 表示新数组的长度
int nextn = nextTab.length;
// 创建FWD节点并引用新的散列表
ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab);
// 迁移标记
boolean advance = true;
// 完成标记
boolean finishing = false;
/*
* 1. i——表示分配给当前线程任务,执行到的桶位
* 2. bound——表示分配给当前线程任务的下界限制
*/
int i = 0, bound = 0
// 自旋
for (;;) {
/*
* 1. f——桶位的头节点
* 2. fh——桶位头节点的hash
*/
Node<K,V> f; int fh;
while (advance) {
/*
* 1. nextIndex——分配任务的开始下标
* 2. nextBound——分配任务的结束下标
*/
int nextIndex, nextBound;
// 当前线程是否迁移完毕或还未分配任务
// --i >= bound 检查的是是否越界,如果成立说明还有区间的桶位要处理,不成立说明当前线程对应区间的桶位已经全部迁移完毕或还未分配迁移任务。
if (--i >= bound || finishing)
advance = false;
// 来到这里说明当前线程已经完成了迁移任务,再查看全局是否有区间可以分配,如果成立说明没有区间可以分配了
else if ((nextIndex = transferIndex) <= 0) {
// 设置要处理的桶位索引是-1表示没有桶位要处理了
i = -1;
// 迁移标记是false
advance = false;
}
/*
* 1. 走到这个情况说明:当前线程需要分配任务区间,全局范围内还有桶位尚未迁移
* 2. 使用CAS更新 TRANSFERINDEX
* 3. 成功说明给当前线程分配任务成功
* 4. 维护bound,i,advance 等变量
*/
else if (U.compareAndSwapInt
(this, TRANSFERINDEX, nextIndex,
nextBound = (nextIndex > stride ?
nextIndex - stride : 0))) {
bound = nextBound;
i = nextIndex - 1;
advance = false;
}
}
// 当前线程没有分配到任务
if (i < 0 || i >= n || i + n >= nextn) {
// sizeCtl
int sc;
// 最后一个退出的线程要做的事
if (finishing) {
// 设置新的散列表为null
nextTable = null;
// 设置给旧的散列表
table = nextTab;
// 设置扩容阈值为当前散列表长度的百分之75
sizeCtl = (n << 1) - (n >>> 1);
return;
}
// 使用CAS将sizeCtl的低16位-1(参与并发扩容的线程数)
if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) {
// 如果不是最后一个退出的线程
if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT)
return;
// 如果是最后一个推出的线程
finishing = advance = true;
// 设置迁移桶位为旧散列表的长度
i = n;
}
}
// 要被迁移的桶位是个null
else if ((f = tabAt(tab, i)) == null)
// 使用casTabAt设置桶位是一个FWD节点
advance = casTabAt(tab, i, null, fwd);
/*
* 1. 要被迁移的桶位不是null
* 2. 要被迁移的桶位是个FWD节点
*/
else if ((fh = f.hash) == MOVED)
// 设置迁移标记是true
advance = true;
// 要被迁移的桶位是链表或红黑树
else {
// 锁住桶位
synchronized (f) {
// 再次检查
if (tabAt(tab, i) == f) {
/*
* 1. ln——低位链表引用
* 2. hn——高位链表引用
*/
Node<K,V> ln, hn;
// 如果当前桶位是链表
if (fh >= 0) {
// 通过lastRun机制取出当前链表的末尾连续不变的Node
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;
}
// 遍历当前链表到lastRun,把这一段链表拆分为低位链表和高位链表(形成链表使用的是头插法)
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);
}
// 设置低位链表到新散列表中的i号下标的桶位
setTabAt(nextTab, i, ln);
// 通过setTabAt设置高位链表到nextTab
setTabAt(nextTab, i + n, hn);
// 通过setTabAt设置当前桶位是FWD节点
setTabAt(tab, i, fwd);
// 设置推进标记advance = true;
advance = true;
}
// 要被迁移的桶位形成红黑树
else if (f instanceof TreeBin) {
// 把桶位节点强转为TreeBin节点
TreeBin<K,V> t = (TreeBin<K,V>)f;
// 低位双向链表的头与尾
TreeNode<K,V> lo = null, loTail = null;
// 高位双向链表的头与尾
TreeNode<K,V> hi = null, hiTail = null;
/*
* 1. lc——低位链表元素数量
* 2. hc——高位链表元素数量
*/
int lc = 0, hc = 0;
// 遍历TreeBin中的链表,形成低位双向链表与高位双向链表
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;
}
}
// 如果低位链表的长度满足降级为链表的阈值则降级,反过来在判断高位链表长度是否大于0,如果大于0说明高低位链表均有元素需要构造为TreeBin,反过来就是只有低位链表,那么说明把原先的链表移到相应位置即可。(高位链表处理一样)
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;
}
}
}
}
}
}
2.方法作用说明
迁移数据的方法,把旧的散列表中的数据迁移到nextTable中。
3.方法入参说明
- tab: 旧table的引用
- nextTab: 新的table的引用,如果是扩容线程这个就是null,如果是辅助扩容线程这个就不是null
4.方法变量说明
- n: 扩容之前table数组的长度
- stride: 表示分配给线程任务的步长
- nextn: 表示新数组的长度
- fwd: FWD节点里面有新表的引用
- advance: 迁移标记
- finishing: 完成标记
- i: 表示分配给当前线程任务,执行到的桶位
- bound: 表示分配给当前线程任务的下界限制
- f: 桶位的头节点
- fh: 桶位头节点的hash
- nextIndex: 分配任务的开始下标
- nextBound: 分配任务的结束下标
- sc: sizeCtl
- ln: 低位链表引用
- hn: 高位链表引用
- lo: 低位双向链表的头
- loTail: 低位双向链表的尾
- hi: 高位双向链表的头
- hiTail: 高位双向链表的尾
- lc: 低位链表元素数量
- hc: 高位链表元素数量
5:情况分析
- nextTable为null扩容线程初始化nextTable
- 分配迁移任务前的检查
- 当前线程是否迁移完毕或还未分配任务
- 全局范围内桶位是否都处理完毕了
- 分配迁移任务
- 退出逻辑
- 迁移逻辑
- 要被迁移的桶位是个null
- 要被迁移的桶位是个FWD
- 要被迁移的桶位形成了链表
- 要被迁移的桶位形成了红黑树
第一种情况:nextTable为null扩容线程初始化nextTable
源代码
if (nextTab == null) { // initiating
try {
@SuppressWarnings("unchecked")
Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n << 1];
nextTab = nt;
} catch (Throwable ex) { // try to cope with OOME
sizeCtl = Integer.MAX_VALUE;
return;
}
nextTable = nextTab;
transferIndex = n;
}
代码分析
初始化一个长度为原来table两倍的数组,并且赋值给全局的nextTable和设置transferIndex为原来table的长度。
第二种情况:当前线程是否迁移完毕或还未分配任务
源代码
if (--i >= bound || finishing)
advance = false;
代码分析
–i >= bound 检查的是是否越界,如果成立说明还有区间的桶位要处理,不成立说明当前线程对应区间的桶位已经全部迁移完毕或还未分配迁移任务。
第三种情况:全局范围内桶位是否都处理完毕了
源代码
else if ((nextIndex = transferIndex) <= 0) {
i = -1;
advance = false;
}
代码分析
只有第二种情况不成立才会来到这个情况,成立说明素有桶位都已经分配完毕了,没有区间可以分配了。
第四种情况:分配迁移任务
源代码
else if (U.compareAndSwapInt
(this, TRANSFERINDEX, nextIndex,
nextBound = (nextIndex > stride ?
nextIndex - stride : 0))) {
bound = nextBound;
i = nextIndex - 1;
advance = false;
}
代码分析
- 走到这个情况说明:当前线程需要分配任务区间,全局范围内还有桶位尚未迁移
- 使用CAS更新 TRANSFERINDEX
- 成功说明给当前线程分配任务成功
- 维护bound,i,advance 等变量
第五种情况:退出逻辑
源代码
if (i < 0 || i >= n || i + n >= nextn) {
int sc;
if (finishing) {
nextTable = null;
table = nextTab;
sizeCtl = (n << 1) - (n >>> 1);
return;
}
if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) {
if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT)
return;
finishing = advance = true;
i = n; // recheck before commit
}
}
代码分析
如果线程没有分配到任务会来到退出逻辑,退出有两种情况,第一种是最后退出的线程,第二种是不是最后退出的线程。
不是最后退出的线程
- 使用CAS将sizeCtl的低16位-1(参与并发扩容的线程数)
- 如果成功则判断是不是最后退出的线程如果不是则直接return
是最后退出的线程
- 使用CAS将sizeCtl的低16位-1(参与并发扩容的线程数)
- 如果是最后退出的线程需要一些收尾工作:从数组的最大索引开始检查节点是不是FWD,设置nextTable是null,把nextTab设置给table,设置sizeCtl为新的扩容阈值(扩容后table的75%)。
第六种情况:要被迁移的桶位是个null
代码分析
else if ((f = tabAt(tab, i)) == null)
advance = casTabAt(tab, i, null, fwd);
代码分析
获取当前要被迁移的桶位,如果是null,那么就使用CAS设置为FWD节点。
第七种情况:要被迁移的桶位是个FWD节点
源代码
else if ((fh = f.hash) == MOVED)
advance = true;
代码分析
如果成立则说明对应桶位节点是FWD节点,则设置 advance = true;
第八种情况:要被迁移的桶位形成链表
源代码
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;
}
代码分析
- 创建高低位链表的引用
- 通过lastRun机制取出当前链表的末尾连续不变的Node
- 遍历当前链表到lastRun,把这一段链表拆分为低位链表和高位链表(形成链表使用的是头插法)
- 通过setTabAt设置低位链表到nextTab
- 通过setTabAt设置高位链表到nextTab
- 通过setTabAt设置当前桶位是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;
}
代码分析
- 遍历TreeBin中的链表,形成低位双向链表与高位双向链表
- 如果低位链表的长度满足降级为链表的阈值则降级,反过来在判断高位链表长度是否大于0,如果大于0说明高低位链表均有元素需要构造为TreeBin,反过来就是只有低位链表,那么说明把原先的链表移到相应位置即可。(高位链表处理一样)
核心方法helpTransfer解析
1.源代码
final Node<K,V>[] helpTransfer(Node<K,V>[] tab, Node<K,V> f) {
/*
* 1. nextTab——引用nextTable
* 2. sc——sizeCtl
*/
Node<K,V>[] nextTab; int sc;
/*
* 1. tab != null——旧的散列表一定不等于null
* 2. (f instanceof ForwardingNode)——必须是通过FWD节点进来的
* 3. (nextTab = ((ForwardingNode<K,V>)f).nextTable) != null——把f强转为FWD类型并获取nextTable引用并且不是null
*/
if (tab != null && (f instanceof ForwardingNode) &&
(nextTab = ((ForwardingNode<K,V>)f).nextTable) != null) {
// 获取扩容标识戳
int rs = resizeStamp(tab.length);
/*
* 1. nextTab == nextTable——表示散列表正在扩容
* 2. table == tab——说明还在扩容中
* 3. (sc = sizeCtl) < 0——说明散列表正在扩容中
*/
while (nextTab == nextTable && table == tab &&
(sc = sizeCtl) < 0) {
/*
* 1. (sc >>> RESIZE_STAMP_SHIFT) != rs——取出sc的高16位是否等于当前线程的扩容戳,如果成立说明扩容标识戳不相等不能参与扩容。
* 2. sc == rs + 1——作者写错了,想写的是sc == (rs << 16) +1,如果成立表示本次扩容完毕了。
* 3. sc == rs + MAX_RESIZERS——作者写错了,想写的是sc == (rs << 16) + MAX_RESIZERS,如果成立表示参与扩容线程数量已经达到最大值(65534)。
* 4. (nt = nextTable) == null——本次扩容结束
* 5. transferIndex <= 0——全局的扩容进度已经小于等于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;
}
2:方法入参说明
- tab: 成员变量table
- f: 当前线程对应的桶位节点
3:方法变量说明
- nextTab: f是一个FWD节点,nextTable的引用
- sc: sizeCtl
- rs: 扩容标识戳
核心方法get解析
1.源代码
public V get(Object key) {
/*
* 1. tab——引用散列表
* 2. e——当前元素
* 3. p——目标节点
* 4. n——散列表长度
* 5. eh——当前元素hash
* 6. ek——当前元素key
*/
Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
// 计算key的hash
int h = spread(key.hashCode());
/*
* 1. (tab = table) != null——散列表已经初始化
* 2. (n = tab.length) > 0——散列表长度大于0
* 3. (e = tabAt(tab, (n - 1) & h)) != null——获取key对应的桶位对象并且不是null
*/
if ((tab = table) != null && (n = tab.length) > 0 &&
(e = tabAt(tab, (n - 1) & h)) != null) {
// 如果桶位对象的hash值等于要查找key的hash值
if ((eh = e.hash) == h) {
// 如果key相等则返回桶位对象的value
if ((ek = e.key) == key || (ek != null && key.equals(ek)))
return e.val;
}
// 如果是桶位节点是FWD节点或TreeBin节点,则调用桶位对象的find方法进行查询
else if (eh < 0)
return (p = e.find(h, key)) != null ? p.val : null;
// 走到这里说明当前桶位形成了链表,遍历链表
while ((e = e.next) != null) {
// 如果当前节点key相等则返回value
if (e.hash == h &&
((ek = e.key) == key || (ek != null && key.equals(ek))))
return e.val;
}
}
return null;
}
2.方法变量说明
- tab: table数组
- e: 当前元素
- p: 目标节点
- n: table数组长度
- eh: 当前元素hash
- ek: 当前元素的key
3:情况说明
- 散列表没有初始化
- key对应的桶位是null
- key对应的桶位不是null
- key与桶位的key一致
- 与链表中的某个元素的key一致
- 与红黑树中的某个元素的key一致
- 桶位是FWD节点需要到新表中查询
第一种与第二种情况:散列表没有初始化,key对应的桶位是null
源代码
if ((tab = table) != null && (n = tab.length) > 0 &&
(e = tabAt(tab, (n - 1) & h)) != null)
代码分析
- 判断散列表不等于null并且长度大于0保证了散列表已经初始化了。
- tabAt方法获取散列表指定下标的元素并且不等于null
第三种情况:key与桶位的key一致
源代码
if ((eh = e.hash) == h) {
if ((ek = e.key) == key || (ek != null && key.equals(ek)))
return e.val;
}
代码分析
判断桶位对象的hash与key的hash是否相等,如果相等再判断key是否相等。
第四种情况:与链表中的某个元素的key一致
源代码
while ((e = e.next) != null) {
if (e.hash == h &&
((ek = e.key) == key || (ek != null && key.equals(ek))))
return e.val;
}
代码分析
遍历链表,如果有key相同的则返回
第五种情况:红黑树与FWD
源代码
else if (eh < 0)
return (p = e.find(h, key)) != null ? p.val : null;
代码分析
- eh<0 hash值小于0说明是FWD或红黑树情况
- 调用FWD或TreeBin节点的find方法
核心方法remove(replaceNode)解析
1.源代码
final V replaceNode(Object key, V value, Object cv) {
// 计算key的hash值
int hash = spread(key.hashCode());
// 引用全局的散列表和自旋
Node<K,V>[] tab = table;
for (;;) {
/*
* 1. f——key对应的桶位对象
* 2. n——tab的length
* 3. i——key对应的table下标
* 4. fh——key对应桶位元素的hash
*/
Node<K,V> f; int n, i, fh;
/*
* 有两种情况会直接break
* 1. tab == null || (n = tab.length) == 0——散列表没有初始化
* 2. (f = tabAt(tab, i = (n - 1) & hash)) == null——key对应的桶位节点是null
*/
if (tab == null || (n = tab.length) == 0 ||
(f = tabAt(tab, i = (n - 1) & hash)) == null)
break;
/*
* 当条件满足说明当前散列表正在扩容需要去帮助扩容
* 1. 散列表已经初始化并且key对应的桶位不是null
* 2. (fh = f.hash) == MOVED——key对应的桶位节点是FWD节点
*/
else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
// 走到这里说明当前桶位形成的不是链表就是红黑树
else {
// 以前的value
V oldVal = null;
// 校验标记
boolean validated = false;
// 对当前桶位加锁,因为下面的逻辑会对当前桶位形成的链表或红黑树进行删除或替换操作所以需要保证线程安全
synchronized (f) {
// 再次检查,防止在上锁之前有其他线程对这个桶位进行过修改或散列表已经扩容过,造成对应桶位下标的对象与锁的对象不一致
if (tabAt(tab, i) == f) {
// 如果对应桶位形成的是链表
if (fh >= 0) {
// 设置校验标记为true
validated = true;
// 自旋遍历链表,声明链表头节点
for (Node<K,V> e = f, pred = null;;) {
// 链表中当前节点的key
K ek;
// 如果hash值相等并且key也相等
if (e.hash == hash &&
((ek = e.key) == key ||
(ek != null && key.equals(ek)))) { // 获取key对应的value
V ev = e.val;
/*
* 1. cv == null——说明是个删除操作
* 2. cv == ev——cv和对应的value相等
* 3. (ev != null && cv.equals(ev))——cv和对应的value相等
*/
if (cv == null || cv == ev ||
(ev != null && cv.equals(ev))) {
// 赋值给原先的value
oldVal = ev;
// 如果value不等于null那就替换
if (value != null)
e.val = value;
// 是删除操作但是当前节点并不是链表中的头元素所以需要让上一个节点的引用指向当前节点的下一个引用
else if (pred != null)
pred.next = e.next;
// 是删除操作并且删除的是头节点,那么让链表头的下一个节点成为链表头
else
setTabAt(tab, i, e.next);
}
break;
}
// 在每一次循环都会保存当前节点的引用把它赋值给pred
pred = e;
// 如果条件成立说明到达了链表尾部还没有与其匹配的key直接结束循环
if ((e = e.next) == null)
break;
}
}
// 说明当前桶位形成了红黑树
else if (f instanceof TreeBin) {
// 设置校验标记为true
validated = true;
// 把桶位节点强制转换为TreeBin节点
TreeBin<K,V> t = (TreeBin<K,V>)f;
/*
* 1. r——红黑树根节点
* 2. p——表示红黑树中查找到对应key一致的Node
*/
TreeNode<K,V> r, p;
/*
* 执行删除或替换的前提
* 1. (r = t.root) != null——红黑树有数据
* 2. (p = r.findTreeNode(hash, key, null)) != null——调用红黑树根节点的findTreeNode方法去查询对应的节点并且这个节点不为null
*/
if ((r = t.root) != null &&
(p = r.findTreeNode(hash, key, null)) != null) {
// 获取要被删除或替换的节点的value
V pv = p.val;
/*
* 1. cv == null——不需要比较
* 2. cv == ev——cv和对应的value相等
* 3. (ev != null && cv.equals(ev))——cv和对应的value相等
*/
if (cv == null || cv == pv ||
(pv != null && cv.equals(pv))) {
// 赋值给oldVal
oldVal = pv;
// 替换操作
if (value != null)
p.val = value;
// 删除操作,并且把红黑树转换为链表
else if (t.removeTreeNode(p))
setTabAt(tab, i, untreeify(t.first));
}
}
}
}
}
// 如果校验标记是true,只有走了链表或红黑树才会是true
if (validated) {
// 如果存在旧值,说明存在删除或替换操作
if (oldVal != null) {
// 如果value是null说明是删除操作需要维护散列表元素数量
if (value == null)
addCount(-1L, -1);
return oldVal;
}
break;
}
}
}
return null;
}
2.方法作用说明
找到对应的key并替换value,如果cv传了的话需要cv等于对应的value,如果后两个参数是null那么就是remove。
3:方法入参说明
- key: 指定key
- value: 要替换的value
- cv: 要比较的value
4:方法变量说明
- hash: key经过扰动函数后计算出来的hash值
- tab: 引用table
- f: key对应的桶位对象
- n: tab的length
- i: key对应的table下标
- fh: key对应桶位元素的hash
- r: 红黑树根节点
- p: 表示红黑树中查找到对应key一致的Node
5:情况说明
- 散列表没有初始化
- key对应桶位是null
- key对应桶位是FWD,调用helpTransfer帮助扩容
- key对应桶位形成链表或只有桶位
- key对应桶位形成红黑树
- 如果是删除调用addCount方法-1
核心方法treeifyBin解析
1.源代码
private final void treeifyBin(Node<K,V>[] tab, int index) {
/*
* 1. b——散列表的引用
* 2. n——散列表的长度
* 3. sc——sizeCtl
*/
Node<K,V> 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) {
// 桶位加锁
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);
if ((p.prev = tl) == null)
hd = p;
else
tl.next = p;
tl = p;
}
// 把双向链表的头给TreeBin的构造方法让其构造出红黑树
setTabAt(tab, index, new TreeBin<K,V>(hd));
}
}
}
}
}
2.方法作用说明
根据索引下标,判断是否要树化该桶位的链表,如果需要则树化。
3:方法入参说明
- tab: 散列表的引用
- index: 索引
4:方法变量说明
- b: 桶位对象
- n: 散列表长度
- sc: sizeCtl,没用到
核心内部类TreeBin解析
1.源代码
static final class TreeBin<K,V> extends Node<K,V> {
TreeNode<K,V> root;
volatile TreeNode<K,V> 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
/**
* Tie-breaking utility for ordering insertions when equal
* hashCodes and non-comparable. We don't require a total
* order, just a consistent insertion rule to maintain
* equivalence across rebalancings. Tie-breaking further than
* necessary simplifies testing a bit.
*/
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<K,V> b) {
super(TREEBIN, null, null, null);
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);
break;
}
}
}
}
this.root = r;
assert checkInvariants(root);
}
/**
* Acquires write lock for tree restructuring.
*/
private final void lockRoot() {
if (!U.compareAndSwapInt(this, LOCKSTATE, 0, WRITER))
contendedLock(); // offload to separate method
}
/**
* Releases write lock for tree restructuring.
*/
private final void unlockRoot() {
lockState = 0;
}
/**
* Possibly blocks awaiting root lock.
*/
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);
}
}
/**
* Returns matching node or null if none. Tries to search
* using tree comparisons from root, but continues linear
* search when lock not available.
*/
final Node<K,V> find(int h, Object k) {
if (k != null) {
for (Node<K,V> e = first; e != null; ) {
int s; K ek;
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<K,V> 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;
}
/**
* Finds or adds a node.
* @return null if added
*/
final TreeNode<K,V> putTreeVal(int h, K k, V v) {
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;
}
/**
* Removes the given node, that must be present before this
* call. This is messier than typical red-black deletion code
* because we cannot swap the contents of an interior node
* with a leaf successor that is pinned by "next" pointers
* that are accessible independently of lock. So instead we
* swap the tree linkages.
*
* @return true if now too small, so should be untreeified
*/
final boolean removeTreeNode(TreeNode<K,V> p) {
TreeNode<K,V> next = (TreeNode<K,V>)p.next;
TreeNode<K,V> pred = p.prev; // unlink traversal pointers
TreeNode<K,V> r, rl;
if (pred == null)
first = next;
else
pred.next = next;
if (next != null)
next.prev = pred;
if (first == null) {
root = null;
return true;
}
if ((r = root) == null || r.right == null || // too small
(rl = r.left) == null || rl.left == null)
return true;
lockRoot();
try {
TreeNode<K,V> replacement;
TreeNode<K,V> pl = p.left;
TreeNode<K,V> pr = p.right;
if (pl != null && pr != null) {
TreeNode<K,V> s = pr, sl;
while ((sl = s.left) != null) // find successor
s = sl;
boolean c = s.red; s.red = p.red; p.red = c; // swap colors
TreeNode<K,V> sr = s.right;
TreeNode<K,V> pp = p.parent;
if (s == pr) { // p was s's direct parent
p.parent = s;
s.right = p;
}
else {
TreeNode<K,V> sp = s.parent;
if ((p.parent = sp) != null) {
if (s == sp.left)
sp.left = p;
else
sp.right = p;
}
if ((s.right = pr) != null)
pr.parent = s;
}
p.left = null;
if ((p.right = sr) != null)
sr.parent = p;
if ((s.left = pl) != null)
pl.parent = s;
if ((s.parent = pp) == null)
r = s;
else if (p == pp.left)
pp.left = s;
else
pp.right = s;
if (sr != null)
replacement = sr;
else
replacement = p;
}
else if (pl != null)
replacement = pl;
else if (pr != null)
replacement = pr;
else
replacement = p;
if (replacement != p) {
TreeNode<K,V> pp = replacement.parent = p.parent;
if (pp == null)
r = replacement;
else if (p == pp.left)
pp.left = replacement;
else
pp.right = replacement;
p.left = p.right = p.parent = null;
}
root = (p.red) ? r : balanceDeletion(r, replacement);
if (p == replacement) { // detach pointers
TreeNode<K,V> 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;
}
/* ------------------------------------------------------------ */
// Red-black tree methods, all adapted from CLR
static <K,V> TreeNode<K,V> rotateLeft(TreeNode<K,V> root,
TreeNode<K,V> p) {
TreeNode<K,V> r, pp, rl;
if (p != null && (r = p.right) != null) {
if ((rl = p.right = r.left) != null)
rl.parent = p;
if ((pp = r.parent = p.parent) == null)
(root = r).red = false;
else if (pp.left == p)
pp.left = r;
else
pp.right = r;
r.left = p;
p.parent = r;
}
return root;
}
static <K,V> TreeNode<K,V> rotateRight(TreeNode<K,V> root,
TreeNode<K,V> p) {
TreeNode<K,V> l, pp, lr;
if (p != null && (l = p.left) != null) {
if ((lr = p.left = l.right) != null)
lr.parent = p;
if ((pp = l.parent = p.parent) == null)
(root = l).red = false;
else if (pp.right == p)
pp.right = l;
else
pp.left = l;
l.right = p;
p.parent = l;
}
return root;
}
static <K,V> TreeNode<K,V> balanceInsertion(TreeNode<K,V> root,
TreeNode<K,V> x) {
x.red = true;
for (TreeNode<K,V> xp, xpp, xppl, xppr;;) {
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);
xpp = (xp = x.parent) == null ? null : xp.parent;
}
if (xp != null) {
xp.red = false;
if (xpp != null) {
xpp.red = true;
root = rotateLeft(root, xpp);
}
}
}
}
}
}
static <K,V> TreeNode<K,V> balanceDeletion(TreeNode<K,V> root,
TreeNode<K,V> x) {
for (TreeNode<K,V> 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);
xpr = (xp = x.parent) == null ? null : xp.right;
}
if (xpr == null)
x = xp;
else {
TreeNode<K,V> 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) {
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<K,V> 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 <K,V> boolean checkInvariants(TreeNode<K,V> t) {
TreeNode<K,V> tp = t.parent, tl = t.left, tr = t.right,
tb = t.prev, tn = (TreeNode<K,V>)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);
}
}
}
2.属性说明
- root: 红黑树的根节点
- first: 链表的头节点
- waiter: 等待者线程(当前lockState是读锁状态)
- lockState: 锁状态(写锁状态,读锁状态)
- WRITER: 常量1,表示写锁状态
- WAITER: 常量2,等待者状态(一定是个写线程在等待)
- READER: 常量4,表示读锁状态(每一个读线程都会+4)
3.核心方法说明
构造方法解析
源代码
TreeBin(TreeNode<K,V> b) {
// 设置节点hash为-2,表示此节点是TreeBin节点
super(TREEBIN, null, null, null);
// 使用first属性引用TreeNode组成的双向链表
this.first = b;
// 红黑树的根节点
TreeNode<K,V> r = null;
// 遍历双向链表,x表示当前元素,next表示下一个节点
for (TreeNode<K,V> x = b, next; x != null; x = next) {
next = (TreeNode<K,V>)x.next;
// 强制设置当前插入节点的左右子树为null
x.left = x.right = null;
// 如果成立,说明当前红黑树,是一个空树,那么设置插入元素为根节点
if (r == null) {
x.parent = null;
x.red = false;
r = x;
}
// 非第一次循环都会来到else分支,说明根节点已经有元素了
else {
// 链表中当前元素的key
K k = x.key;
// 链表中当前元素的value
int h = x.hash;
// 表示插入节点key的class
Class<?> kc = null;
// 自旋,p表示为查找插入节点的父节点的一个临时节点
for (TreeNode<K,V> p = r;;) {
/*
* 1. dir——只有两个值-1和1,-1表示插入节点的hash值小于当前节点p的hash,1就是反过来
* 2. ph——表示为查找插入节点的父节点的一个临时节点的hash
/
int dir, ph;
// 临时节点的key
K pk = p.key;
// 插入节点的hash值小于当前节点的hash值
if ((ph = p.hash) > h)
// 说明要去插入到左边
dir = -1;
// 插入节点的hash值大于当前节点的hash值
else if (ph < h)
// 说明要去插入到右边
dir = 1;
// 如果执行到这里说明当前插入节点的hash与当前临时节点的hash一致,会在这里通过Class进行最终的顺序确定
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);
break;
}
}
}
}
// 设置属性的根节点root
this.root = r;
assert checkInvariants(root);
}
方法作用说明
根据双向链表的表头,去构建红黑树。
方法变量说明
- r: 红黑树的根节点
- x: 遍历链表时的当前节点
- next: 遍历链表时当前节点的下一个节点
- k: 链表中当前节点key
- h: 链表中当前节点的hash
- kc: 表示插入节点key的class
- p: 遍历红黑树时的当前节点,初始化为根节点,在自旋最后会更新引用为做自己节点或右子节点
- dir: 表示链表中当前节点与红黑树中当前节点的大小的比对结果,-1为小,1为大
- ph: 遍历红黑树时的当前节点的hash
- xp: 找到插入位置的父节点的引用
find方法解析
源代码
final Node<K,V> find(int h, Object k) {
// key不是null
if (k != null) {
// 遍历TreeBin中的链表,e代表遍历时的当前元素
for (Node<K,V> e = first; e != null; ) {
/*
* 1. s——lock临时状态
* 2. ek——链表当前节点的key
*/
int s; K ek;
/*
* 1. (WAITER|WRITER)——按位或的结果是二进制的0011也就是十进制的3
* 2. lockState & 0011 != 0——如果成立说明lockState要么是1要么是2
* 条件总结:如果这个TreeBin节点上有写线程那么当前读线程就去链表读否则就就去红黑树读
*/
if (((s = lockState) & (WAITER|WRITER)) != 0) {
// 遍历链表查看是否右相同的key如果有则返回
if (e.hash == h &&
((ek = e.key) == k || (ek != null && k.equals(ek))))
return e;
e = e.next;
}
// 来到这里说明 lockState是4,使用CAS的方式对lockState的低位+4(加了一把读锁)
else if (U.compareAndSwapInt(this, LOCKSTATE, s,
s + READER)) {
/*
* 1. r——查询的根节点
* 2. p——查询的结果节点
*/
TreeNode<K,V> r, p;
try {
// 使用TreeNode的findTreeNode方法进行查询
p = ((r = root) == null ? null :
r.findTreeNode(h, k, null));
} finally {
// 表示等待着线程
Thread w;
/*
* 1. U.getAndAddInt(this, LOCKSTATE, -READER)——释放读锁
* 2. (READER|WAITER)——二进制就是0110十进制的6
* 3. (w = waiter) != null——说明有一个写线程在等待,读操作全部结束
* 条件总结:释放读锁,当前是否是最后一个读线程并且是否有线程在等待,如果是则把等待线程唤醒
*/
if (U.getAndAddInt(this, LOCKSTATE, -READER) ==
(READER|WAITER) && (w = waiter) != null)
LockSupport.unpark(w);
}
return p;
}
}
}
return null;
}
方法作用说明
根据key与hash去链表或红黑树中查询。
方法变量说明
- e: 遍历TreeBin中的链表,e代表遍历时的当前元素
- s: lockState
- ek: 链表当前节点的key
- r: 红黑树的根节点
- p: 红黑树查询命中的节点
- w: 表示在等待的线程
putTreeVal方法解析
源代码
final TreeNode<K,V> putTreeVal(int h, K k, V v) {
// 这段代码是为了确定要把节点插入到那里,在TreeBin的构造方法有详细的写过了这里不在解读
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;
// 如果成立说明当前遍历的节点就是要插入节点的父节点(xp)
if ((p = (dir <= 0) ? p.left : p.right) == null) {
/*
* 1. x——表示插入节点
* 2. f——表示修改链表之前的链表的头节点
*/
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 {
/*
* 调整红黑树结构前需要加写锁,这个方法用CAS把lockState从0修改到1(如果没有读锁就会加锁成功)
* 如果加写锁之前有读线程:修改lockState低位+2,设置waiter线程为当前线程,使用LockSupport.park方法挂起当前线程
* 如果加写锁之前没有读线程:使用CAS再次修改lockState为1,并且设置waiter属性为null
*/
lockRoot();
try {
// 平衡红黑树
root = balanceInsertion(root, x);
} finally {
// 释放锁,就是使用CAS把lockState设置为0
unlockRoot();
}
}
break;
}
}
assert checkInvariants(root);
return null;
}