我目前正在阅读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块,但是我仍然不清楚为什么在ifwhile中需要两个条件。

    最佳答案

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

    关于java - Java ConcurrentHashMap初始化,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/61722136/

    10-10 10:59