Java Concurrent Hashmap initTable () Perché il blocco try / latest?

Aug 29 2020

Ho esaminato il seguente codice (proveniente da qui ) '

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

Qualcuno può spiegare perché è necessario il blocco try?

Risposte

3 rzwitserloot Aug 29 2020 at 20:31

come concetto esistono eccezioni ed errori non controllati. Se chiami .put()un ConcurrentHashMap e sei veramente a corto di memoria, quella mappa potrebbe provare a creare un array e quella chiamata potrebbe fallire con un OutOfMemoryError. Il codice continuerà ancora (al blocco catch che cattura questo, forse) e i riferimenti a quella mappa esistono ancora. Sarebbe un po 'una schifezza se, dopo ciò, la mappa si blocca e viene completamente invalidata perché sizeCtl ha un valore non funzionante.

Il punto è quel blocco finale, che ripristina il sizeCtlvalore. Viene utilizzato per varie cose, tra cui la gestione di quale thread ottiene l'accesso. Se non lo facesse, qualsiasi altra chiamata put girerebbe per sempre.

La rilevanza di questo (come in, quanto spesso si verifica un oggetto lanciabile qui, rispetto alla rimozione del "tentativo" e del "finalmente" e alla fine sizeCtl = sc;senza l'overhead aggiuntivo di provare / infine) è bassa, ma se è rilevante, è abbastanza rilevante.

Den-Jason Aug 29 2020 at 20:32

È una specie di mutex e una serratura a doppio controllo.

In primo luogo

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

controlla il sizeCtlmutex. Se è inferiore a zero, qualcun altro lo ha afferrato, quindi aspettiamo solo un po '.

Se è zero (o più), è probabile che otteniamo il blocco. Quindi viene eseguito un confronto e scambio:

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

Questa è un'operazione atomica: se un altro thread lo afferrasse tra il primo e il secondo controllo, ifnon verrebbe eseguito.

Se il CAS ha esito positivo, il metodo diventa responsabile del rilascio del mutex. Pertanto, a try-finallyviene utilizzato per garantire che il mutex venga rilasciato ( sizeCtlripristinato al suo valore originale) indipendentemente dal fatto che si verifichi un'eccezione (ad esempio memoria insufficiente che assegna il nuovo nodo - o se esiste una restrizione univoca applicabile alla mappa) o meno.