hashMap为什么是线程不安全的?
HashMap在多线程环境下可能出现线程不安全的问题,具体原因如下:
非线程同步:HashMap是非线程同步的数据结构,它不会对多线程并发操作进行任何同步措施。这意味着在多个线程同时对HashMap进行修改操作时,可能导致数据不一致或丢失。
结构修改导致的问题:当一个线程在进行结构修改(如put、remove等)的同时,另一个线程正在进行遍历操作(如迭代器遍历),可能导致遍历过程中的ConcurrentModificationException异常,或者遍历结果不准确。
线程间可见性问题:由于缺乏同步机制,对HashMap的修改操作可能无法立即被其他线程感知到。这可能导致一个线程在读取HashMap的时候,看到的是过期的或不一致的数据。
请问有读过ConcurrentHashMap源码吗?知道它用了哪些锁吗?
CAS与synchronized来保证线程安全。ConcurrentHashMap怎么做到高效有线程安全的????
HashTable每个方法都是用了synchronized同步代码快所以效率低。
3.1 初始化方法
/**
-1:表示有线程正在进行真正的初始化操作
-(1+nThreads):表示有nThreads个线程正在进行扩容操作
>0:表示接下来的初始化操作中使用的容量,或者初始化/扩容完成后的threshold
=0:默认值
volatile:保证了可见性
**/
private transient volatile int sizeCtl;
/**初始化map:使用sizeCtl中记录的大小初始化表
1、判断sizeCtl属性是否为-1,如果为-1则表示有其他线程正在初始化map,则该线程自旋等待
如果大于0则使用乐观锁CAS锁定资源,进而进行初始化map流程*/
private final Node<K,V>[] initTable() {
Node<K,V>[] tab; int sc;
while ((tab = table) == null || tab.length == 0) {
/**当sezeCtl为-1说明当前有线程在对数组初始化*/
if ((sc = sizeCtl) < 0)
/**放弃cpu资源*/
Thread.yield(); // lost initialization race; just spin
/**通过cas对sizectl减一,如果成功了,说明当前线程拿到锁*/
else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
try {
if ((tab = table) == null || tab.length == 0) {
int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
@SuppressWarnings("unchecked")
Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
table = tab = nt;
sc = n - (n >>> 2);
}
} finally {
sizeCtl = sc;
}
break;
}
}
return tab;
}
3.2 put方法
场景:如果两个线程去put值在同一个位置,会出现竞争的情况,解决方案是什么?
put方法流程:
判断传入的键值是否为空,如果为空则报控制正;
计算KEY的哈希值与上数组长度,确定entity在数组中的位置;
1、首先循环数组,判断数组长度是否为0,如果为0则进入inittable方法;
2、先判断该数组下标位置是否为空,如果为空则通过cas的方式设置值,否则就自旋等待;
3、然后再判断这个数组是否正在扩容,如果正在扩容,则帮助扩容;判断的依据是链表第一个节点hash是否被设置成MOVED = -1
4、现在则证明key对应的数组下标有值,则对该节点加锁,然后还会对该节点做一个cas二次校验
5、然后判断该节点是数节点点还是链表节点。
5.1、如果为链表节点则则循环链表,判断如果遇到相同的节点元素,则直接替换
5.2、如果是数节点则走树的添加元素流程
6、然后判断是否达到树化条件,如果达到树化条件则走树化流程
7、计算size+1(难点,存在并发的情况)
put方法添加元素
final V putVal(K key, V value, boolean onlyIfAbsent) {
if (key == null || value == null) throw new NullPointerException();
int hash = spread(key.hashCode());
/**记录了数组结构修改的次数*/
int binCount = 0;
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;
if (tab == null || (n = tab.length) == 0)
tab = initTable();
/**1、总结:这个位置有两种情况 a:如果这个位置为空就通过cas的方式在数组的i位置设置值,b:如果cas失败了这个位置已经有其他线 程进行更改,这个位置有值则说明其他线程设置了值就退出循环*/
/**先算key的hash值确定数组下标这个位置是否位空*/
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
/**通过cas的方式向数组的位置设置值*/
if (casTabAt(tab, i, null,
new Node<K,V>(hash, key, value, null)))
/**如果cas成功了就break*/
break; // no lock when adding to empty bin
}
/**f相当于一个node对象,如果它的hash值为-1则表示整个map正在扩容*/
else if ((fh = f.hash) == MOVED)
/**帮助其他线程进行扩容,多个线程对map扩容优势就是效率高*/
tab = helpTransfer(tab, f);
/**正常put元素,两种情况:该节点的下一个节点是链表还是红黑树*/
else {
V oldVal = null;
/**1.7之前用的分段锁,1.8使用synchronized:它对链表中的第一个节点加了一把锁*/
synchronized (f) {
/**当加锁的过程很有可能其他线程来对这个头节点加锁了,所以这里需要判断一下,头节点是否加锁*/
if (tabAt(tab, i) == f) {
if (fh >= 0) {
binCount = 1;
for (Node<K,V> e = f;; ++binCount) {
K ek;
if (e.hash == hash &&
((ek = e.key) == key ||
(ek != null && key.equals(ek)))) {
oldVal = e.val;
if (!onlyIfAbsent)
e.val = value;
break;
}
Node<K,V> pred = e;
if ((e = e.next) == null) {
pred.next = new Node<K,V>(hash, key,
value, null);
break;
}
}
}
else if (f instanceof TreeBin) {
Node<K,V> p;
binCount = 2;
if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
value)) != null) {
oldVal = p.val;
if (!onlyIfAbsent)
p.val = value;
}
}
}
}
if (binCount != 0) {
if (binCount >= TREEIFY_THRESHOLD)
treeifyBin(tab, i);
if (oldVal != null)
return oldVal;
break;
}
}
}
addCount(1L, binCount);
return null;
}
//***************************************************************************************************
final V putVal(K key, V value, boolean onlyIfAbsent) {
if (key == null || value == null) throw new NullPointerException();
// 检查传入的键值是否为null,如果是,则抛出NullPointerException异常
int hash = spread(key.hashCode());
// 计算传入键的哈希值,并使用spread方法对哈希值进行扩散操作
int binCount = 0;
// 记录哈希桶中元素的数量
for (Node<K,V>[] tab = table;;) {
// 迭代哈希表的桶
Node<K,V> f; int n, i, fh;
// 定义变量f(桶上的第一个节点),n(哈希表桶数组的长度),i(计算的哈希值对应的桶索引),fh(节点的哈希值)
if (tab == null || (n = tab.length) == 0)
tab = initTable();
// 如果哈希表为空,则初始化哈希表
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
// 如果哈希桶上的第一个节点为空,说明该桶为空
/**
tabAt: 哈希表中查找指定位置的桶(bucket),并判断该桶是否为空。下面对代码进行详细解释:
tabAt(tab, i = (n - 1) & hash): 这是一个辅助方法 tabAt,用于获取哈希表 tab 中索引为 i 的位置上的桶。同时,i = (n - 1) & hash 是为了计算出桶的索引位置。其中 n 是哈希表的长度(容量),hash 是要查找的键的哈希值。
f = tabAt(tab, i): 将索引为 i 的桶赋值给变量 f。如果 f 的值为 null,表示该桶为空。
if (f == null) { ... }: 如果 f 的值为 null,即该桶为空,进入 if 分支的代码块。
在这段代码中,通过计算出桶的索引位置,然后通过 tabAt 方法获取到该位置上的桶。如果桶为空,即没有发现任何元素,可以执行一些操作,比如在该位置插入新的键值对或者进行其他的操作。
总的来说,这段代码是在哈希表中查找指定位置的桶,并判断该桶是否为空。如果为空,可以执行相应的操作。
*/
if (casTabAt(tab, i, null,
new Node<K,V>(hash, key, value, null)))
break; // 当往空桶里添加元素时,不需要加锁
}
else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
// 如果哈希桶上的第一个节点的哈希值为MOVED,说明正在扩容,需要帮助进行转移操作
else {
V oldVal = null;
synchronized (f) {
// synchronized锁定该桶的第一个节点
if (tabAt(tab, i) == f) {
// 双检查,确保当前线程获取到的桶和第一次获取的桶是一致的
if (fh >= 0) {
// 如果哈希桶上的第一个节点的哈希值大于等于0,说明该桶为链表节点
binCount = 1;
// 初始化哈希桶的元素数量为1
for (Node<K,V> e = f;; ++binCount) {
K ek;
//它检查当前节点 e 的哈希值与要查找的键的哈希值是否相等,然后再检查键是否相等
if (e.hash == hash &&
((ek = e.key) == key ||
(ek != null && key.equals(ek)))) {
oldVal = e.val;
// 找到相同的键,记录旧值
if (!onlyIfAbsent)
e.val = value;
// 如果onlyIfAbsent为false,则更新节点的值为新值
break;
}
Node<K,V> pred = e;
if ((e = e.next) == null) {
// 遍历到链表尾部,未找到相同的键,将新节点插入到链表尾部
pred.next = new Node<K,V>(hash, key, value, null);
break;
}
}
}
else if (f instanceof TreeBin) {
// 如果哈希桶上的第一个节点是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;
// 如果onlyIfAbsent为false,则更新节点的值为新值
}
}
}
}
if (binCount != 0) {
// 如果binCount不为0,说明元素已经添加到哈希桶中
if (binCount >= TREEIFY_THRESHOLD)
treeifyBin(tab, i);
// 如果哈希桶中的元素数量超过树化阈值,则将链表转换为红黑树
if (oldVal != null)
return oldVal;
// 如果找到相同的键,返回旧值
break;
}
}
}
addCount(
3.3 addCount(计算size+1)
负载因子为什么是0.75
负载因子是一个衡量哈希表填充程度的指标,它表示哈希表中已经存储的元素数量与哈希表容量的比值。
负载因子的选择是一个折中考虑的问题。如果负载因子设置得过小,即元素数量与容量的比值较低,那么哈希表将会比较稀疏,浪费了空间资源。此时,虽然哈希冲突的概率会降低,但在查找元素时会增加遍历链表或树的成本,从而影响了性能。
相反,如果负载因子设置得过大,即元素数量与容量的比值较高,那么哈希表将会比较拥挤,容易发生哈希冲突。这会导致链表或树的长度增加,查找、插入和删除操作的性能可能会下降。
在综合考虑空间利用率和性能的情况下,一般选择一个合适的负载因子。对于ConcurrentHashMap来说,默认的负载因子是0.75,经验上认为这个值在平衡空间利用率和性能方面有较好的表现。在大多数情况下,0.75的负载因子可以提供相对较高的空间利用率同时保持较低的哈希冲突概率,从而提供较好的性能。
3、扩容方法 size+1
1、数组size+1的过程
addCount :map添加元素之后计算数组size+1(相当于计算数组大小,但是这里存在多线程竞争的情况),如果大于扩容阈值就走扩容方法 问题size+1怎么控制线程安全
关键问题size怎么统计的
1、每个线程都会生成一个随机数 随机数&length-1 找到在CounterCell数组中的位置(注意:CounterCell数组是单独的用于计数的数组)。
2、线程都对自己映射的数组位置的value进行CAS操作
a、如果竞争成功,那么就对该位置上的value进行+1操作
private final void addCount(long x, int check) {
/**CounterCell[] 计数数组
1、每个线程都会生成一个随机数 随机数&length-1 数组定位 */
CounterCell[] as; long b, s;
/**关键:!!(性能压榨)判断在数组不为空的情况下则直接进入代码块,否则数组为空则去CAS baseCount*/
if ((as = counterCells) != null ||
/**CAS BASECOUNT 成功的话,就size+1,然后取!非就为真,则进入代码块*/
!U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) {
CounterCell a; long v; int m;
boolean uncontended = true;
/**这里有三个条件慢慢来看
第一个条件:as数组长度为空则直接调用fullAddCount方法进行数组初始化,并赋值。如果数组长度不为空则进入下面
第二个条件:数组长度是否小于0,本质上和第一个条件一样*/
if (as == null || (m = as.length - 1) < 0 ||
/**第三个条件:走到这里就说明数组以及被初始化了,那么就直接对对应的位置进行cas赋值,如果CAS成功,取非则if条件不满住直接进入判断map是否需要扩容流程*/
(a = as[ThreadLocalRandom.getProbe() & m]) == null ||
!(uncontended =
U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) {
/**如果进入该方法则证明数组还未初始化,就开始对CounterCell进行初始化,然后对value+1*/
fullAddCount(x, uncontended);
return;
}
if (check <= 1)
return;
/**计算当前map中元素的个数。*/
s = sumCount();
}
/**判断是否需要进行扩容*/
if (check >= 0) {
Node<K,V>[] tab, nt; int n, sc;
while (s >= (long)(sc = sizeCtl) && (tab = table) != null &&
(n = tab.length) < MAXIMUM_CAPACITY) {
int rs = resizeStamp(n);
if (sc < 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))
transfer(tab, null);
s = sumCount();
}
}
}
//*******************************************************************************************
/**
* Adds to count, and if table is too small and not already
* resizing, initiates transfer. If already resizing, helps
* perform transfer if work is available. Rechecks occupancy
* after a transfer to see if another resize is already needed
* because resizings are lagging additions.
*
* @param x the count to add
* @param check if <0, don't check resize, if <= 1 only check if uncontended
*/
private final void addCount(long x, int check) {
CounterCell[] as; long b, s;
// 如果 counterCells 数组不为空或者无法原子更新 baseCount,则执行下面的代码块
if ((as = counterCells) != null || !U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) {
CounterCell a; long v; int m;
boolean uncontended = true;
// 如果 counterCells 数组为空或者无法原子更新 counterCells 数组中的某个 CounterCell 的值,则执行下面的代码块
if (as == null || (m = as.length - 1) < 0 ||
(a = as[ThreadLocalRandom.getProbe() & m]) == null ||
!(uncontended = U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) {
// 处理竞争情况,将 x 添加到计数值中
fullAddCount(x, uncontended);
return;
}
// 如果 check 参数小于等于 1,则直接返回
if (check <= 1)
return;
// 重新计算当前的计数值
s = sumCount();
}
// 如果 check 参数大于等于 0,则执行后续的代码块
if (check >= 0) {
Node<K,V>[] tab, nt; int n, sc;
// 循环判断当前计数值和哈希表的状态,决定是否进行扩容操作
while (s >= (long)(sc = sizeCtl) && (tab = table) != null &&
(n = tab.length) < MAXIMUM_CAPACITY) {
int rs = resizeStamp(n);
// 如果 sizeCtl 小于 0,表示当前正在进行扩容操作
if (sc < 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))
transfer(tab, null);
// 重新计算当前的计数值
s = sumCount();
}
}
}
3.4 fullAddCount()方法-如果CAS baseCount失败执行
在竞争basecount失败后就调用fullAddCount()方法在baseCount上面计数
绕的地方别绕进去了:如果有一个线程两次cas,并且重新算了hash定位的数组下标都有值的话就会对数组进行扩容,
相当于一个空间换时间思想。
// See LongAdder version for explanation
private final void fullAddCount(long x, boolean wasUncontended) {
int h;
/**线程生成随机数,用于后续定位操作数组的位置*/
if ((h = ThreadLocalRandom.getProbe()) == 0) {
ThreadLocalRandom.localInit(); // force initialization
h = ThreadLocalRandom.getProbe();
wasUncontended = true;
}
/**是否冲突*/
boolean collide = false; // True if last slot nonempty
for (;;) {
CounterCell[] as; CounterCell a; int n; long v;
/**第一种情况,CounterCell已经被初始化了*/
if ((as = counterCells) != null && (n = as.length) > 0) {
/**判断该线程操作的数组位置是否为空-如果为空进入代码块*/
if ((a = as[(n - 1) & h]) == null) {
/**判断cellBusy变量是否改变,就是说该数组是否被其他线程占用-等于0则代表空闲*/
if (cellsBusy == 0) { // Try to attach new Cell
/**然后就new一个CounterCell实例,准备放入数组里面*/
CounterCell r = new CounterCell(x); // Optimistic create
/**在放入之前,还要判断这个数组是否被占用,*/
if (cellsBusy == 0 &&
/**如果没有被占用,则通过cas的方式把(cellsbusy)的值修改为1,则相当于对当前的数组加锁*/
U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {
/**声明一个标志,来判断是否被修改成功,false代表没有修改成功*/
boolean created = false;
try { // Recheck under lock
CounterCell[] rs; int m, j;
/**判断,如果这个数组不为空,我才把值放在数组对应的位置*/
if ((rs = counterCells) != null &&
(m = rs.length) > 0 &&
rs[j = (m - 1) & h] == null) {
rs[j] = r;
/**到这个里则证明修改成功*/
created = true;
}
} finally {
/**释放锁*/
cellsBusy = 0;
}
/**如果修改成功则退出*/
if (created)
break;
/**如果失败则继续尝试*/
continue; // Slot is now non-empty
}
}
collide = false;
}
/**第三个情况*/
//走到这里说明,自己对应数组的位置不为空,已经有值了,于是重新生成hash,再重试
/**cas失败之后,重新生成hash,计算对应数组的下标*/
/**第二次走到这里的时候这个!wasUncontended为false条件不满足然后走下面逻辑代码*/
else if (!wasUncontended) // CAS already known to fail
wasUncontended = true; // Continue after rehash
/**通过cas的方式对数组位置加1,如果加成功了就返回*/
/**线程如果两次cas修改都失败了的情况下(相当于两次循环对应的数组位置下标的地方都有值),会走下面对数组进行扩容*/
else if (U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))
break;
/**控制扩容条件:如果有其他线程对数组进行改变,就不会去扩容
还有一个条件就是如果线程数大于cpu核心数就不会去扩容*/
else if (counterCells != as || n >= NCPU)
collide = false; // At max size or stale
/**collide:冲突的意思,如果当collide为true的时候会进行扩容*/
else if (!collide)
collide = true;
/**cas去拿到数组的使用权,并对数组进行扩容*/
else if (cellsBusy == 0 &&
U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {
try {
/**二次检验*/
if (counterCells == as) {// Expand table unless stale
/**对数组进行扩容,扩容为原来两倍*/
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
}
/**当第一次cas失败后重新算hash值,确定数组位置下标,*/
h = ThreadLocalRandom.advanceProbe(h);
}
//第二种情况:表示这个数组还没有初始化--cellsBusy表示当前数组有没有其他线程在使用
else if (cellsBusy == 0 && counterCells == as &&
/**通过cas的方式把cellBusy修改为1,相当于在这个对数组(加锁)*/
U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {
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;
}
if (init)
//初始化成功直接break
break;
}
//如果在两个线程竞争的情况下,对cellsBusy修改失败的话(因为只有一个线程才能够对共享变量修改)则去 CASbaseCount
else if (U.compareAndSwapLong(this, BASECOUNT, v = baseCount, v + x))
break; // Fall back on using base
}
}
3.5 transfer()扩容方法
/**
* Moves and/or copies the nodes in each bin to new table. See
* above for explanation.
*/
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; // subdivide range
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;
}
int nextn = nextTab.length;
ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab);
boolean advance = true;
boolean finishing = false; // to ensure sweep before committing nextTab
for (int i = 0, bound = 0;;) {
Node<K,V> f; int fh;
while (advance) {
int nextIndex, nextBound;
if (--i >= bound || finishing)
advance = false;
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;
}
}
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
}
}
else if ((f = tabAt(tab, i)) == null)
advance = casTabAt(tab, i, null, fwd);
else if ((fh = f.hash) == MOVED)
advance = true; // already processed
else {
synchronized (f) {
if (tabAt(tab, i) == f) {
Node<K,V> ln, hn;
if (fh >= 0) {
int runBit = fh & n;
Node<K,V> lastRun = f;
for (Node<K,V> p = f.next; p != null; p = p.next) {
int b = p.hash & n;
if (b != runBit) {
runBit = b;
lastRun = p;
}
}
if (runBit == 0) {
ln = lastRun;
hn = null;
}
else {
hn = lastRun;
ln = null;
}
for (Node<K,V> p = f; p != lastRun; p = p.next) {
int ph = p.hash; K pk = p.key; V pv = p.val;
if ((ph & n) == 0)
ln = new Node<K,V>(ph, pk, pv, ln);
else
hn = new Node<K,V>(ph, pk, pv, hn);
}
setTabAt(nextTab, i, ln);
setTabAt(nextTab, i + n, hn);
setTabAt(tab, i, fwd);
advance = true;
}
else if (f instanceof TreeBin) {
TreeBin<K,V> t = (TreeBin<K,V>)f;
TreeNode<K,V> lo = null, loTail = null;
TreeNode<K,V> hi = null, hiTail = null;
int lc = 0, hc = 0;
for (Node<K,V> e = t.first; e != null; e = e.next) {
int h = e.hash;
TreeNode<K,V> p = new TreeNode<K,V>
(h, e.key, e.val, null, null);
if ((h & n) == 0) {
if ((p.prev = loTail) == null)
lo = p;
else
loTail.next = p;
loTail = p;
++lc;
}
else {
if ((p.prev = hiTail) == null)
hi = p;
else
hiTail.next = p;
hiTail = p;
++hc;
}
}
ln = (lc <= UNTREEIFY_THRESHOLD) ? untreeify(lo) :
(hc != 0) ? new TreeBin<K,V>(lo) : t;
hn = (hc <= UNTREEIFY_THRESHOLD) ? untreeify(hi) :
(lc != 0) ? new TreeBin<K,V>(hi) : t;
setTabAt(nextTab, i, ln);
setTabAt(nextTab, i + n, hn);
setTabAt(tab, i, fwd);
advance = true;
}
}
}
}
}
}