我目前正在阅读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. long==0)
来检查table
不为null且其长度不为0并确定是否继续循环,因此它仅在两个条件均为false时退出。我只是想不出table
不为null但长度为0的情况,因为它是在else
块中初始化的,其中n始终被赋予大于0的值。那为什么我们需要两者而不是只有一个?2.第二个问题不是问题,而是我只想检查我的假设是否有意义。所以想象两个线程随后以非常接近的方式运行。如果较早的线程没有到达table
的初始化点或place 2也没有更新sizeCtl
,那么第二个线程将能够进入else if
块,因此table
字段将再次初始化?这种情况会在极少数情况下发生吗?我知道这不会影响程序的正确性,但在某些情况下会发生吗?
希望任何人能给我一个澄清。事先感谢。
更新:我忘记了一个成功的CAS操作会将sizeCtl
交换为-1,这将阻止其他线程进入else if
块,但我仍然不清楚为什么我们需要if
和while
中的两个条件。
单独分析initTable
方法是不够的。您需要考虑其他方法,这些方法也可以修改table
和sizeCtl
字段。在那里您会注意到,例如传输
使用CAS将sizeCtl
减少了一个。我不确定这是否会导致任何时间点的table. long==0
,但请注意,整个过程要复杂得多,initTable
可以与调整大小操作同时调用。