Java simultâneo Hashmap initTable () Por que o bloco try / finally?

Aug 29 2020

Estive olhando o seguinte código (proveniente daqui ) '

/**
 * 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;
}

Alguém pode explicar por que é necessário o bloco try?

Respostas

3 rzwitserloot Aug 29 2020 at 20:31

exceções não verificadas e erros existem como um conceito. Se você chamar .put()um ConcurrentHashMap e estiver realmente com pouca memória, esse mapa pode tentar fazer uma matriz e essa chamada pode falhar com um OutOfMemoryError. O código ainda continuará (no bloco catch que captura isso, talvez), e as referências a esse mapa ainda existem. Seria uma merda se, depois disso, o mapa travasse e fosse completamente invalidado porque sizeCtl tem um valor quebrado.

O ponto é aquele bloco finalmente, que restaura o sizeCtlvalor. Isso é usado para várias coisas, incluindo o gerenciamento de qual thread obtém acesso. Do contrário, quaisquer outras chamadas put girariam para sempre.

A relevância disso (como em, quantas vezes um lançável ocorre aqui, versus remover o 'try' e o 'finally' e apenas terminar com sizeCtl = sc;sem a sobrecarga adicional de try / finally) é baixa, mas se for relevante, é bastante relevante.

Den-Jason Aug 29 2020 at 20:32

É uma espécie de mutex - e um bloqueio de verificação dupla.

primeiramente

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

verifica o sizeCtlmutex. Se for menor que zero, outra pessoa o agarrou, então esperamos um pouco.

Se for zero (ou mais), é provável que obtenhamos o bloqueio. Portanto, uma comparação-e-troca é realizada:

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

Esta é uma operação atômica - se outro encadeamento o agarrasse entre a primeira e a segunda verificação, o ifnão seria obtido.

Se o CAS for bem-sucedido, o método se torna responsável por liberar o mutex. Portanto, a try-finallyé usado para garantir que o mutex seja liberado ( sizeCtlredefinido para seu valor original) independentemente de ocorrer uma exceção (por exemplo, falta de memória atribuindo o novo nó - ou se houver uma restrição exclusiva aplicável ao mapa) ou não.