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

#java #concurrenthashmap

#java #concurrenthashmap

Вопрос:

Я просматривал следующий код (полученный из здесь)’

 /**
 * 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?

Ответ №1:

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

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

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

Комментарии:

1. Странно.. Я опубликовал эту возможность здесь. youtube.com/… Итак, моя догадка совпадает с вашей.

Ответ №2:

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

Во-первых

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

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

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

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

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

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

Комментарии:

1. Как это может быть правильным, потому что выход происходит вне try?

2. Посмотрите на это как на желание зайти в действительно маленький магазин. Он может вместить только одного человека. Итак, вы подходите к двери и заглядываете внутрь. Если вы видите, что кто-то там уже есть, вместо того, чтобы ждать, вы уходите и делаете что-то еще немного (‘yield’). Если вы видите, что магазин пуст, вы переходите к дескриптору — но кто-то другой может опередить вас в этом — отсюда и CAS. Имеет ли это смысл?

3. try-finally требуется только после того, как метод настроен на доступ к ресурсу. Сродни закрытию двери после выхода из магазина, даже если владелец нелюбезно вышвыривает вас 😉

4. Для тех, кто ищет приличную книгу по многопоточности Java, я рекомендую «Параллельное программирование на Java» Дуга Ли. amazon.co.uk /…