Java ConcurrentHashMap 初始化

3

我正在阅读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;
    }
}

主要的困惑点集中在以下几个方面:
1. 为什么我们需要while((tab = table) == null || tab.length == 0)来检查table不是null且其长度不为0,并确定是否继续循环,因此只有当这两个条件都为false时才退出循环。我无法想象出table不为null但长度为0的情况,因为它是在else if块中初始化的,而n始终被赋一个大于0的值。那么为什么我们需要两个条件而不是只需要其中一个? 更新: 我忘了一件事,即一次成功的CAS操作将sizeCtl交换为-1,这将阻止其他线程进入else if块,但我仍然不清楚为什么需要在ifwhile中使用两个条件。
希望有人能为我澄清一下。谢谢。

1
compareAndSwap是原子性的,这个检查确保只有一个线程可以进入该块。 - Louis Wasserman
@LouisWasserman 感谢您的提示,是的,我忘记了交换,如果一个线程进入块,则sizeCtl已经交换为-1,因此会阻止其他线程进入该块,对吧?但第一点呢?您能否请解释一下? - Boyu Zhang
1个回答

1

仅分析initTable方法是不够的。您需要考虑其他可以修改tablesizeCtl字段的方法。您会注意到,例如,transfer使用CAS将sizeCtl减少了一个。我不确定这是否会导致在任何时候table.length == 0,但请注意整个过程要复杂得多,并且initTable可以与调整大小操作同时调用。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接