Java concurrente Hashmap initTable () ¿Por qué el bloque try / finalmente?

Aug 29 2020

He estado mirando el siguiente código (obtenido de aquí ) '

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

¿Alguien puede explicar por qué es necesario el bloque try?

Respuestas

3 rzwitserloot Aug 29 2020 at 20:31

Las excepciones y errores no controlados existen como concepto. Si llama .put()a un ConcurrentHashMap, y tiene mucha memoria, ese mapa puede intentar hacer una matriz y esa llamada puede fallar con un OutOfMemoryError. El código continuará (en el bloque de captura que detecta esto, quizás), y las referencias a ese mapa aún existen. Sería una mierda si, después de que eso suceda, el mapa se bloquee y se invalide por completo porque sizeCtl tiene un valor roto.

El punto es ese bloque finalmente, que restaura el sizeCtlvalor. Esto se usa para varias cosas, entre las que se incluye la administración de qué hilo obtiene acceso. Si no fuera así, cualquier otra opción put giraría para siempre.

La relevancia de esto (como en, con qué frecuencia ocurre un lanzamiento aquí, frente a eliminar el 'intento' y el 'finalmente' y simplemente terminar sizeCtl = sc;sin la sobrecarga adicional de probar / finalmente) es baja, pero si es relevante, es bastante relevante.

Den-Jason Aug 29 2020 at 20:32

Es una especie de mutex y un bloqueo de doble verificación.

en primer lugar

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

comprueba el sizeCtlmutex. Si es menor que cero, alguien más lo ha agarrado, así que esperamos un poco.

Si es cero (o más), es probable que obtengamos el bloqueo. Entonces se realiza una comparación e intercambio:

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

Esta es una operación atómica: si otro hilo lo tomara entre la primera y la segunda verificación, ifno se tomaría.

Si el CAS tiene éxito, entonces el método se vuelve responsable de liberar el mutex. Por lo tanto, try-finallyse usa para asegurar que el mutex sea liberado ( sizeCtlrestablecido a su valor original) independientemente de si ocurre una excepción (por ejemplo, falta de memoria al asignar el nuevo Nodo o si hay una restricción única aplicable al mapa) o no.