(1)首先是Collections.synchronizedList方法需要传入一个List的实现类,根据其实现类是否为RandomAccess,
否则进入内部类SynchronizedList进行初始化;
public static <T> List<T> synchronizedList(List<T> list) {
return (list instanceof RandomAccess ?
new SynchronizedRandomAccessList<>(list) :
new SynchronizedList<>(list));//初始化成员变量和锁对象
}
(2)其次无论实现类是啥,最终都是通过SynchronizedList的父类SynchronizedCollection来初始化mute和list成员变量;
SynchronizedCollection类:
final Collection<E> c;
final Object mutex;
SynchronizedCollection(Collection<E> var1) {
this.c = (Collection)Objects.requireNonNull(var1);//初始化成员变量
this.mutex = this;//当前对象为锁对象
}
(3)最后使用JU集合类调用被Collections.synchronizedList包装后的add、get、remove等方法,
实际上会被添加synchronized锁保证线程安全,最终还是会调用不安全的方法。
SynchronizedCollection:
public boolean add(E e) {
synchronized (mutex) {return c.add(e);}//加锁并调用不安全方法
}
public boolean remove(Object o) {
synchronized (mutex) {return c.remove(o);}//加锁并调用不安全方法
}
SynchronizedList:
public E get(int var1) {
synchronized(this.mutex) {
return this.list.get(var1);//加锁并调用不安全方法
}
}
总结:
使用线程不安全的集合类如ArrayList、LinkedList等来被Collections.synchronizedList包装后,
只是对不安全线程方法添加了synchronized保证线程安全,实际底层还是调用不安全集合类方法。
原理:
使用JU集合类调用Collections.synchronizedMap方法后对m和mute进行初始化,
同时调用基础方法都添加了synchronized方法保证线程安全,然后调用底层不安全方法。
SynchronizedMap:
private final Map
final Object mutex; // Object on which to synchronize
SynchronizedMap(Map m) {
this.m = Objects.requireNonNull(m);//初始化成员变量
mutex = this;//设置当前对象为锁对象
}
public V get(Object key) {
synchronized (mutex) {return m.get(key);}//加锁并调用不安全方法
}
public V put(K key, V value) {
synchronized (mutex) {return m.put(key, value);}//加锁并调用不安全方法
}
public V remove(Object key) {
synchronized (mutex) {return m.remove(key);}//加锁并调用不安全方法
}
总结:
使用线程不安全的集合类如HashMap、LinkedHashMap等来被Collections.synchronizedMap包装后,
只是对不安全线程方法添加了synchronized保证线程安全,实际底层还是调用不安全集合类方法。
(1)首先是Collections.synchronizedSet方法需要传入一个Set的实现类,调用内部类SynchronizedSet去初始化;
public static <T> Set<T> synchronizedSet(Set<T> s) {
return new SynchronizedSet<>(s);
}
(2)通过SynchronizedSet的父类SynchronizedCollection来初始化mute、c成员变量;
SynchronizedCollection类:
final Collection<E> c;
final Object mutex;
SynchronizedCollection(Collection<E> var1) {
this.c = (Collection)Objects.requireNonNull(var1);//初始化成员变量
this.mutex = this;//当前对象为锁对象
}
(3)最后使用JU集合类调用被Collections.synchronizedSet包装后的add、remove等方法,
实际上会被添加synchronized锁保证线程安全,最终还是会调用不安全的方法。
SynchronizedCollection:
public boolean add(E e) {
synchronized (mutex) {return c.add(e);}//加锁并调用不安全方法
}
public boolean remove(Object o) {
synchronized (mutex) {return c.remove(o);}//加锁并调用不安全方法
}
总结:
使用线程不安全的集合类如HashSet、TreeSet等来被Collections.synchronizedSet包装后,
只是对不安全线程方法添加了synchronized保证线程安全,实际底层还是调用不安全集合类方法。
CopyOnWriteArrayList底层仍然采用数组存储,在get不加锁直接读取索引值,而add在读取数据时是加了非公平锁(ReentrantLock),
防止其他线程同时写入,获取锁执行权后将旧数组拷贝到新容量的数据组(+1),进行设置新值,最后将新数组设置到原数组中,释放锁。
(1)get方法
/**
* 说明:get方法未加锁,是因为add方法添加数据时,采用复制新数组并重新设置数据的方式,
* get方法获取不需要加锁,原因是获取中的值,只是查询索引值,不涉及改数据
*/
public E get(int var1) {//根据索引获取指定值
return this.get(this.getArray(), var1);//调用私有多参数方法
}
private E get(Object[] var1, int var2) {
return var1[var2];//基于存储数组返回指定索引值
}
(2)add方法
/**
* 说明:add方法需要加锁,涉及到数据增加,先获取锁的执行权,然后将旧数组以+1的容量
* 将原数组复制到新数组,设置新值和将新数组赋值给原数组
*/
public boolean add(E var1) {
ReentrantLock var2 = this.lock;//得到锁
var2.lock();//获取锁执行权
boolean var6;//默认false
try {
Object[] var3 = this.getArray();//获取当前存储数组
int var4 = var3.length;//目前已存入的数量
Object[] var5 = Arrays.copyOf(var3, var4 + 1);//基于当前数组复制一个新的数组并+1
var5[var4] = var1;//设置值
this.setArray(var5);//将新数组设置到当前数组
var6 = true;//返回结果
} finally {
var2.unlock();//释放锁
}
return var6;//返回执行结果
}
根据写时复制、读写分离的原理,适用于多读的场景(读不需要加锁),共享读,互斥写,保证数据一致性.即读多写少的场景。
基于ArrayList类型的数据结构,单纯的数组结构,对于在高并发情况下,是无法像HashMap那样开发出ConcurrentHashMap原因如下:
(1)ConcurrentArrayList的数据结构是单纯的数组,不像ConcurrentHashMap是采用数组+链表(+红黑树);
(2)ConcurrentArrayList存储数组要么锁全部list,要么不锁,而ConcurrentHashMap确可以对桶进行加锁,分段实现数据一致性,
提高了并发性;
(3)ConcurrentArrayList无法开发成通用的并发ArrayList,在做了很多限制的情况下,产生了读读共享和读写互斥的CopyOnWriteArrayList,来提高并发性。
通过构造器实例化并发set,其构造器内实际new CopyOnWriteArrayList,为CopyOnWriteArraySet内部成员al赋值,
其内部方法均是通过CopyOnWriteArrayList al变量来调用CopyOnWriteArrayList内的方法,唯一不同的方法如下核心源码。
CopyOnWriteArraySet类:
public boolean add(E e) {
return al.addIfAbsent(e);//通过内部成员调用CopyOnWriteArrayList中的方法
}
CopyOnWriteArrayList类:
public boolean addIfAbsent(E e) {
Object[] snapshot = getArray();//获取当前数组
return indexOf(e, snapshot, 0, snapshot.length) >= 0 ? false :
addIfAbsent(e, snapshot);//判断当前e是否已存在,不存在则添加,存在则直接返回
}
//添加数据方法
private boolean addIfAbsent(E e, Object[] snapshot) {
final ReentrantLock lock = this.lock;//获取锁
lock.lock();//获取锁的执行权
try {
Object[] current = getArray();//重新回去当前数组
int len = current.length;//最新数组长度
if (snapshot != current) {//判断在获取锁前后的数组是否一致
int common = Math.min(snapshot.length, len);//不一致的情况,取较小的数组长度
for (int i = 0; i < common; i++)
if (current[i] != snapshot[i] && eq(e, current[i]))//判断e是否在多线程操作环境中是否被添加
return false;
if (indexOf(e, current, common, len) >= 0)//检验e是否已存在
return false;
}
Object[] newElements = Arrays.copyOf(current, len + 1);//e不存在数组中并新建数组(+1)
newElements[len] = e;//设置值
setArray(newElements);//将新数组设置到存储数组
return true;//返回执行结果
} finally {
lock.unlock();//释放锁
}
}
根据set的无重复性和CopyOnWriteArrayList的有序性,可以得出适用于有序且不能重复的数据存储场景。
(1)ConcurrentHashMap底层结构为Node数据+链表+红黑树+CAS+synchronized;
(2)put整体原理:
第一步是key-value均不能为null;
第二步扰乱算法;
第三步为是否初始化即初始化时会将sizeCtl置为-1,初始化完毕会设置为阈值;链表查找或红黑树查找或键值替换操作都会伴随着
synchronized锁住桶位,且多处替换操作均会使用cas操作如桶位设置、sizeCtl设置值、多线程扩容helpTransfer等。
PS:
sizeCtl各阶段值含义:-1代表正在初始化 0代表程序默认值 >0代表阈值 -N代表正在扩容的线程(N的二进制取低16位-1即为扩容线程,原因是在扩容中左移16位+2)
key-value键值对中的hash值各阶段含义:-1代表正在迁移fwd >0代表key经过扰乱算法后的hash -2代表该桶位为TreeBin节点(作用是快速区分链表)
扩容场景:
第一个是在put中binCount值是否大于等于树化阈值8,treeifyBin方法中判定数组长度是否小于64,来确定是否扩容tryPresize(n << 1);
第二个是在put中由addCount(1L, binCount)方法中判定是数据量是否大于阈值来确定transfer扩容迁移。
扩容步骤:
第一步是先根据数组大小是否超过阈值;
第二步是超过阈值sizeCtl后,再根据sizeCtl是大于0,还是小于0做处理,等于0是初始化时候会考虑;
大于0进行-N(resizeStamp(n))转化后并U.compareAndSwapInt(this, SIZECTL, sc,(rs << RESIZE_STAMP_SHIFT) + 2);
小于0进行U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)转化即多线程进入扩容;
第三步是调用transfer进行扩容迁移,其中位运算扩容,多线程并发分配迁移范围、
推进标记advance、finishing和fwd节点来确定迁移完成,链表的查找、高低位的整体迁移等。
(1)多线程并发扩容,利用NCPU来确定扩容步长;
(2)使用cas来改变sizeCtl值,利用-N的二进制低位16位值M-1来确定多少个线程同时扩容;
(3)在迁移过程中使用hash值与旧数组大小按位与操作,确定每个桶位上的最右边几个连续不需要改的链表节点,直接链接,减少循环的hash操作。
(1)存储结构底层不一样;并发Map是采用Node数组+链表+红黑树+CAS+Synchronized存储和并发,HashTable是使用synchronized来保
证线程安全。
(2)扩容机制不同;并发Map是可以使用多线程并发扩容,且翻倍扩容且容量都是2的幂,HashTable是根据容量翻倍+1进行扩容,非必要2的幂。
(3)Key-Value都是不能为null,并发Map比HashTable性能强。
(1)线程安全:使用CAS+Synchronized来保证数据一致性;
(2)提高性能:利用Cas来控制sizeCtl值变换,根据不同值来进行多线程扩容和迁移,对链表和红黑树节点时利用synchronized保证具体
桶位数据安全,以及不同的hash值(-1,-2和大于0)来代表并发Map是迁移中、红黑树和链表状态。
(1)put方法中定位到当前桶时,根据当前桶位的Node节点Hash值等于MOVED(-1)时则进行helpTransfer进行多线程扩容迁移;
(2)迁移过程:先使用resizeStamp方法获取一个扩容戳,将其左移16位并+1(此时sizeCtl利用CAS是一个非常小的-N),此时低16位-1
则代表此时多少个线程正在扩容,然后根据数组长度确定新的数组和迁移,其中涉及到NCPU和迁移步长、fwd和迁移标志、完成标志来保证迁移。
//提供给外部调用的put方法
public V put(K key, V value) {
return putVal(key, value, false);//调用内部put(或putVal)
}
//实现并发Map添加值
final V putVal(K key, V value, boolean onlyIfAbsent) {
if (key == null || value == null) throw new NullPointerException();//key-value不允许为空
int hash = spread(key.hashCode());//扰乱算法并保证是正数范围内
int binCount = 0;
for (Node<K,V>[] tab = table;;) {//for循环遍历tab
Node<K,V> f; int n, i, fh;
if (tab == null || (n = tab.length) == 0)//判断tab是否被初始化
tab = initTable();//初始化tab
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {//利用扰乱算法后的得到的hash值与数组容量-1进行按位与运算确定桶索引
if (casTabAt(tab, i, null,
new Node<K,V>(hash, key, value, null)))//利用cas判断当前桶位置是否为null,为空则将新的node设置到该位置,否则进去其它分支
break;//设置成功,结束for循环 }
else if ((fh = f.hash) == MOVED)//判断当前f的hash值是否为MOVED(-1代表扩容中正在被迁移)
tab = helpTransfer(tab, f);//进行多线程帮助迁移方法
else {//进入此分支,则代表该桶位置有值,需要被遍历,可能是链表,也可能是红黑树结构
V oldVal = null;
synchronized (f) {//使用synchronized对该桶加锁(分段锁思想)
if (tabAt(tab, i) == f) {//判断锁住的对象为该桶的第一个位置
if (fh >= 0) {//hash值大于等于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)//替换判断,concurrentHashMap的onlyIfAbsent默认为false
e.val = value;//设置新的值
break;
}
Node<K,V> pred = e;//用于链接新node节点的临时变量
if ((e = e.next) == null) {//遍历结束条件
pred.next = new Node<K,V>(hash, key,value, null);//将新的node入链表
break;//结束循环
}
}
}
else if (f instanceof TreeBin) {//判断该桶第一个node是否为红黑树
Node<K,V> p;
binCount = 2;
if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,//红黑树遍历并判断是否被替换的key-value
value)) != null) {
oldVal = p.val;//获取原key对应的value
if (!onlyIfAbsent)//被替换条件,concurrentHashMap的onlyIfAbsent默认为false
p.val = value;//设置新的值
}
}
}
}
if (binCount != 0) {//链表分支中计算器
if (binCount >= TREEIFY_THRESHOLD)//判断是否链表长度大于等于默认阈值8
treeifyBin(tab, i);//树化方法
if (oldVal != null)//替换值是否被赋值
return oldVal;//替换成功后返回旧值
break;
}
}
}
addCount(1L, binCount);//对count进行计算的方法
return null;
}
//扰乱算法:对key的hashcode进行右移16位再取异或,最后与HASH_BITS(0x7fffffff=2148473847)按位与运算,
//保证其值在Integer范围内
static final int spread(int h) {
return (h ^ (h >>> 16)) & HASH_BITS;
}
//初始化table方法
private final Node<K,V>[] initTable() {
Node<K,V>[] tab; int sc;
while ((tab = table) == null || tab.length == 0) {
if ((sc = sizeCtl) < 0)//判断是否在被初始化,-1代表初始化
Thread.yield(); //释放线程执行权即其它线程正在初始化
else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
//利用cas进行比较交换,基于this对象的SIZECTL偏移量的值,对期望值为sc相同,对此偏移量地址进行-1设置
try {
if ((tab = table) == null || tab.length == 0) {//判断table是否被其它线程初始化
int n = (sc > 0) ? sc : DEFAULT_CAPACITY;//判断是否有初始容量值,此时的sc是初始容量
@SuppressWarnings("unchecked")
Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];//初始化数组
table = tab = nt;//赋值
sc = n - (n >>> 2);//计算下次扩容阈值,此时sc为阈值
}
} finally {
sizeCtl = sc;//将阈值赋值给sizeCtl,以后的扩容变量
}
break;//退出while循环
}
}
return tab;//返回初始化完成的数组
}
//帮助table进行迁移
final Node<K,V>[] helpTransfer(Node<K,V>[] tab, Node<K,V> f) {
Node<K,V>[] nextTab; int sc;
//判断当前桶位置是否为ForwardingNode(迁移时使用其作为临时类型,其hash值为-1)并对nextTab进行赋值为ForwardingNode
if (tab != null && (f instance of ForwardingNode) &&
(nextTab = ((ForwardingNode<K,V>)f).nextTable) != null) {
int rs = resizeStamp(tab.length);//获取一个当前数组长度的扩容戳
while (nextTab == nextTable && table == tab &&
(sc = sizeCtl) < 0) {//判断扩容后的数组大小一致和旧的数组一致并且sizeCtl为负值(-N扩容标志)
/**
* 判断sc进行sizeCtl赋值后进行右移16位与扩容戳对比,不一致,则可以进行sc重新赋值,
* 继续判断是否上限和数组是否还有未被分配来扩容的桶
*/
if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
sc == rs + MAX_RESIZERS || transferIndex <= 0)
break;//放弃此线程扩容帮助
if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) {//cas进行设置sizeCtl为sc+1
transfer(tab, nextTab);//进行帮助扩容阶段
break;
}
}
return nextTab;//返回帮助扩容table后的新数组
}
return table;//返回table
}
/**
* Integer.numberOfLeadingZeros该方法的作用是返回无符号整数i的最高非0位前面的0的个数,
* 包括符号位在内:如果n为负数,这个方法将会返回0,符号位为1。
* 如16的二进制表示为0000 0000 0000 0000 0000 0000 0001 0000返回27
* RESIZE_STAMP_BITS为16
*/
static final int resizeStamp(int n) {
//返回一个基于当前n值的一个特定值(最高位非0个数与特殊值进行或运算的值)即扩容戳
return Integer.numberOfLeadingZeros(n) | (1 << (RESIZE_STAMP_BITS - 1));
}
//多线程同时扩容转移方法
private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {
int n = tab.length, stride;
//根据内核(数组长度右移三位和内核确定)和默认步长来确定最小步长
if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)
stride = MIN_TRANSFER_STRIDE; //最小步长为默认的16
if (nextTab == null) {//初始化nextTab
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;//对nextTable成员变量赋值(新扩容的空数组)
transferIndex = n;//转移下标设置原数组的最大下标
}
int nextn = nextTab.length;//新数组的长度
ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab);//使用fwd来作为临时扩容变量
boolean advance = true;//转移进度标志
boolean finishing = false; //转移是否完成标志
for (int i = 0, bound = 0;;) {//转移下标和边界值
Node<K,V> f; int fh;//此桶的第一节点和hash值
//while只是确定i和bound以及transferIndex的赋值
while (advance) {//while循环控制转移
int nextIndex, nextBound;//开始转移的索引和边界值
if (--i >= bound || finishing)//从后往前转移
advance = false;//代表转移完毕,用于跳出循环
else if ((nextIndex = transferIndex) <= 0) {//赋值当前线程负责的转移区域并判断是否转移结束
i = -1;//用于控制if分支
advance = false;//用于控制while循环
}else if (U.compareAndSwapInt(this, TRANSFERINDEX, nextIndex,
nextBound = (nextIndex>stride?nextIndex-stride:0))) {//cas对transferIndex进行计算并赋值
bound = nextBound;//当前线程负责的扩容边界值
i = nextIndex - 1;//if分支处判断语句是大于等于,故要-1
advance = false;//终止while循环,继续走for循环,此时的i和bound都被重新赋值,开始for循环
}
}
if (i < 0 || i >= n || i + n >= nextn) {//
int sc;
if (finishing) {//完成标志
nextTable = null;//变量置空,用于GC回收
table = nextTab;//将旧数组替换为新数组
sizeCtl = (n << 1) - (n >>> 1);//计算新扩容阈值
return;
}
if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) {//cas对sizeCtl-1设置,前面有方法对sizeCtl+1过,需要还原
if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT)//扩容完sc-2刚好与后面条件相等
return;
finishing = advance = true;//设置完成标志
i = n; //提交前检查是否全部迁移完,下面的分支MOVED
}
}
else if ((f = tabAt(tab, i)) == null)//桶验证是否为null
advance = casTabAt(tab, i, null, fwd);//将tab数组下标为i的结点设置为fwd结点,表示迁移
else if ((fh = f.hash) == MOVED)//-1代表正在迁移,说明已经被迁移过了,fwd节点
advance = true; //继续进行迁移中
else {
synchronized (f) {
if (tabAt(tab, i) == f) {//防止f发生变化
Node<K,V> ln, hn;//高低位链表(用于迁移)
if (fh >= 0) {//大于等于0代表链表
int runBit = fh & n;//判断当前n的从右到左第一个为1的位运算,高位是1或者0
Node<K,V> lastRun = f;//当前桶位给lastRun
for (Node<K,V> p = f.next; p != null; p = p.next) {//遍历链表
int b = p.hash & n;//节点的hash与旧容量位运算,确定高位值
/** 两处场景:
* (1)当runBit(fh & n)高位为0时,下面if只有b的高位为1才进入并将1赋值给runBit,将高位头链表给到lastRun
* (2)当runBit(fh & n)高位为1时,下面if只有b的高位为0才进入并将0赋值给runBit,将低位位头链表给到lastRun
* 不断的(1)和(2)高低位变换,直到链表遍历完最后一次变动,记录下不需要变动的lastRun
* 作用:仅仅是确定后面链表(链表从后到前)不需要改变的第一个节点
*/
if (b != runBit) {
runBit = b;
lastRun = p;
}
}
//将不需要改变的第一个节点lastRun记录下,判断记录的是高位还是低位子链表
if (runBit == 0) {//等于0,则上述lastRun记录的是低位
ln = lastRun;
hn = null;
}else {//等于1,则上述lastRun记录的是高位
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);//可能会引用上面记录的lastRun赋值的ln
else//高位放入高链表
hn = new Node<K,V>(ph, pk, pv, hn);//可能会引用上面记录的lastRun赋值的hn
}
setTabAt(nextTab, i, ln);//对低位整体迁移
setTabAt(nextTab, i + n, hn);//对高位整体迁移
setTabAt(tab, i, fwd);//标记当前桶已被迁移完成
advance = true;//进行--i与bound验证,用于while循环
}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);//基于e创建一个TreeNode节点
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;//高位数量自增
}
}
/**
* 低位是否需要解树;
* untreeify解为链表,TreeBin构造方法是变为红黑树;
* hc != 0 在低位中判断是否高位有变化,无变化则直接访问第一个节点t即桶位元素;
*/
ln = (lc <= UNTREEIFY_THRESHOLD) ? untreeify(lo) : (hc != 0) ? new TreeBin<K,V>(lo) : t;
/**
* 高位是否需要解树;
* untreeify解为链表,TreeBin构造方法是变为红黑树;
* lc != 0 在低位中判断是否高位有变化,无变化则直接访问第一个节点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);//标记该桶位为fwd,代表迁移过
advance = true;
}
}
}
}
}
}
//解树操作
static <K,V> Node<K,V> untreeify(Node<K,V> b) {
Node<K,V> hd = null, tl = null;//头尾节点
for (Node<K,V> q = b; q != null; q = q.next) {//遍历TreeNode类型的链表
Node<K,V> p = new Node<K,V>(q.hash, q.key, q.val, null);//构建普通node
if (tl == null)//确定头节点判断
hd = p;//头结点赋值,用于返回
else
tl.next = p;//节点连接
tl = p;//递推连接节点并保证if中条件不成立
}
return hd;//返回头节点
}
//树化为红黑树结构
//此时的b还只是单链表的红黑树
TreeBin(TreeNode<K,V> b) {
super(TREEBIN, null, null, null);//将节点的hash值设置为TREEBIN(-2)
this.first = b;//第一个节点引用TreeNode链表
TreeNode<K,V> r = null;
for (TreeNode<K,V> x = b, next; x != null; x = next) {//遍历b
next = (TreeNode<K,V>)x.next;//递推x
x.left = x.right = null;
if (r == null) {//设置根节点
x.parent = null;//根节点无需父节点
x.red = false;//黑色
r = x;//设置节点值TreeNode
}else {
K k = x.key;//当前节点的key
int h = x.hash;//当前节点的value
Class<?> kc = null;
for (TreeNode<K,V> p = r;;) {//遍历基于r根节点的树结构
int dir, ph;
K pk = p.key;
if ((ph = p.hash) > h)//遍历节点与当前节点判断hash大小
dir = -1;//当前节点小,则走左边,dir为-1
else if (ph < h)//当前节点大于遍历节点
dir = 1;//当前节点大,则走右边,dir为1
else if ((kc == null &&(kc = comparableClassFor(k)) == null) ||
(dir = compareComparables(kc, k, pk)) == 0)//判断hash值相等
dir = tieBreakOrder(k, pk);//tieBreakOrder该方法会得到一个不是0的值即1和-1
//插入节点的父节点
TreeNode<K,V> xp = p;
if ((p = (dir <= 0) ? p.left : p.right) == null) {//根据dir的值进行左右判断
x.parent = xp;//设置父节点
if (dir <= 0)//左子树判定
xp.left = x;//当前节点为左节点
else
xp.right = x;//当前节点为右节点
r = balanceInsertion(r, x);//平衡树结构,包括左旋和右旋,暂不赘述(篇幅过长),前面的HashMap文章中已阐述
break;
}
}
}
}
this.root = r;//根节点为r,平衡树结构
assert checkInvariants(root);//检查此root树是否符合红黑树五大特性
}