Java Concurrent Hashmap initTable () Почему блокировка try / finally?

Aug 29 2020

Я смотрел следующий код ( отсюда ) '

/**
 * Initializes table, using the size recorded in 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.compareAndSetInt(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;
}

Может кто-нибудь объяснить, зачем нужен блок try?

Ответы

3 rzwitserloot Aug 29 2020 at 20:31

непроверенные исключения и ошибки существуют как концепция. Если вы вызываете .put()ConcurrentHashMap и у вас очень мало памяти, эта карта может попытаться создать массив, и этот вызов может завершиться ошибкой OutOfMemoryError. Код по-прежнему будет продолжаться (возможно, в блоке catch, который это улавливает), и ссылки на эту карту все еще существуют. Было бы немного неприятно, если после этого карта выйдет из строя и станет полностью недействительной, потому что sizeCtl имеет неверное значение.

Дело в том, что блок finally, восстанавливающий sizeCtlзначение. Это используется для разных вещей, в том числе для управления тем, какой поток получает доступ. В противном случае любые другие пут-коллы вращались бы вечно.

Актуальность этого (например, как часто здесь происходит бросок, по сравнению с удалением «try» и «finally» и просто завершением sizeCtl = sc;без дополнительных накладных расходов на try / finally) невысока, но если это актуально, это вполне актуально.

Den-Jason Aug 29 2020 at 20:32

Это своего рода мьютекс и блокировка с двойной проверкой.

во-первых

if ((sc = sizeCtl) < 0)
    Thread.yield(); // lost initialization race; just spin

проверяет sizeCtlмьютекс. Если он меньше нуля, это значит, что кто-то другой его схватил, поэтому мы просто немного подождем.

Если он равен нулю (или больше), скорее всего, мы получим блокировку. Итак, выполняется сравнение и замена:

if (U.compareAndSetInt(this, SIZECTL, sc, -1)

Это атомарная операция - если другой поток захватит ее между первой и второй проверками, операция ifне будет выполнена.

Если CAS успешно, тогда метод становится ответственным за освобождение мьютекса. Следовательно, a try-finallyиспользуется для обеспечения того, чтобы мьютекс был освобожден ( sizeCtlсброшен в исходное значение) независимо от того, возникает ли исключение (например, из-за нехватки памяти, назначающей новый узел, или если есть уникальное ограничение, применимое к карте) или нет.