我目前正在阅读jdk1.8中的ConcurrentHashMap的源代码,并且发现initalTable()
方法有点混乱。
public class ConcurrentHashMap<K,V> extends AbstractMap<K,V>
implements ConcurrentMap<K,V>, Serializable {
//some fields revolved in this method
transient volatile Node<K,V>[] table;
private transient volatile int sizeCtl;
private final Node<K,V>[] initTable() {
Node<K,V>[] tab; int sc;
while ((tab = table) == null || tab.length == 0) {
if ((sc = sizeCtl) < 0)
Thread.yield(); // lost initialization race; just spin
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];//place 1
table = tab = nt;//place 2
sc = n - (n >>> 2);
}
} finally {
sizeCtl = sc;
}
break;
}
}
return tab;
}
}
因此,主要的困惑主要集中在以下几点:
while((tab = table) == null || tab.length == 0)
来检查table
都不为null且其长度不为0并确定是否继续循环,因此仅当两个条件都为false时才退出。我只是没有想到table
不为null但长度为0的情况,因为它是在else if
块中初始化的,其中n总是被赋予大于0的值。那么为什么我们需要两者而不是仅一个? 2.第二个问题不是问题,而是我只想检查一下我的假设是否合理。因此,想象两个线程随后以非常紧密的方式运行。如果较早的线程尚未到达
else if
的初始化点或第2位,或者尚未更新table
,则后一个线程可以进入sizeCtl
块,然后第二个线程将能够进入else if
块,因此table
字段将被初始化再一次?这种情况在极少数情况下会发生吗?我知道这不会影响程序的正确性,但是会在某些情况下发生吗?希望任何人都可以给我澄清。提前致谢。
更新:
我忘记了一个成功的CAS操作将
sizeCtl
交换为-1的事实,这将阻止其他线程进入else if
块,但是我仍然不清楚为什么在if
和while
中需要两个条件。 最佳答案
单独分析initTable
方法是不够的。您需要考虑其他方法,这些方法也可以修改table
和sizeCtl
字段。在那里您会注意到transfer
使用CAS将sizeCtl
减一。我不确定这是否会在任何时间点导致table.length == 0
,但请注意,整个过程要复杂得多,并且可以与resize操作同时调用initTable
。
关于java - Java ConcurrentHashMap初始化,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/61722136/