Análisis de código fuente de bloqueo compartido CountDownLatch

CountDownLatch no explica los escenarios de uso de CountDownLatch, puede leer otros artículos. Aquí solo se analiza el código fuente.

método de espera

public void await() throws InterruptedException {
    sync.acquireSharedInterruptibly(1);
}

Llame directamente al método adquiridoSharedInterruptbly de AbstractQueuedSynchronizer

  public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        if (tryAcquireShared(arg) < 0)
            doAcquireSharedInterruptibly(arg);
    }

Adquirir en modo compartido y cancelar si se interrumpe. Primero verificando el estado de la interrupción, luego llamando a tryAcquireShared al menos una vez y regresando exitosamente. De lo contrario, el hilo se pondrá en cola y puede bloquearse y desbloquearse repetidamente, y llamar a tryAcquireShared hasta que tenga éxito o se interrumpa el hilo. 

   protected int tryAcquireShared(int acquires) {
            return (getState() == 0) ? 1 : -1;
        }

 Cuando el valor de estado en CountDownLatch es 0, el bloqueo se desbloquea y todas las demás condiciones están bloqueadas. Por lo tanto, el método tryAcquireShared de CountDownLatch determinará si el estado es igual a 0. Si es igual a 0, no se bloqueará y devolverá 1. Si no es igual a 0 (mayor que 0), devolverá -1, y entrará en la cola de bloqueo hasta que se desbloquee.

 
   private void doAcquireSharedInterruptibly(int arg)
        throws InterruptedException {
        final Node node = addWaiter(Node.SHARED);
        boolean failed = true;
        try {
            for (;;) {
                final Node p = node.predecessor();
                if (p == head) {
                    int r = tryAcquireShared(arg);
                    if (r >= 0) {
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        failed = false;
                        return;
                    }
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

1. Esto es diferente de la llamada de bloqueo exclusiva addWaiter, el parámetro aquí es Node.SHARED (modo compartido)

2. Si el nodo frontal es la cabeza, intente adquirir el bloqueo, r> = 0 significa estado == 0, no es necesario bloquear,

3. Si el nodo frontal no es cabeza, o r <0, bloquea el hilo actual y establece el nodo frontal en -1 (SEÑAL). Esperando ser despertado

4. Independientemente de la interrupción, después de despertar, siga el bucle for para ejecutar el paso 2. Si la adquisición aún falla, continúe bloqueando.

método countDown

Disminuya el recuento del pestillo y, si el recuento llega a cero, libere todos los subprocesos en espera. Si el recuento actual es mayor que cero, se reduce. Si el nuevo recuento es cero, todos los subprocesos en espera se volverán a habilitar para la programación de subprocesos. Si el recuento actual es igual a cero, no pasará nada.

 public void countDown() {
        sync.releaseShared(1);
    }

 Llame directamente al método releaseShared de AbstractQueuedSynchronizer

   public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    }

Si el intento de liberar el bloqueo compartido tiene éxito, es decir, el estado se reduce a 0, entonces

El método tryReleaseShared anulado por CountDownLatch

     protected boolean tryReleaseShared(int releases) {
            // Decrement count; signal when transition to zero
            for (;;) {
                int c = getState();
                if (c == 0)
                    return false;
                int nextc = c-1;
                if (compareAndSetState(c, nextc))
                    return nextc == 0;
            }
        }

1. Si el estado actual es 0, devuelva falso y no haga nada

2. Si no es 0, disminuya el estado en 1 y devuelva si se reduce a 0.

AbstractQueuedSynchronizer # doReleaseShared 方法 :

    private void doReleaseShared() {
        for (;;) {
            Node h = head;
            if (h != null && h != tail) {
                int ws = h.waitStatus;
                if (ws == Node.SIGNAL) {
                    if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                        continue;            // loop to recheck cases
                    unparkSuccessor(h);
                }
                else if (ws == 0 &&
                         !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                    continue;                // loop on failed CAS
            }
            if (h == head)                   // loop if head changed
                break;
        }
    }

1. Si el nodo principal no está vacío y no es igual al nodo de cola, significa que hay un nodo bloqueado. Al agregar un nodo de bloqueo, el waitStatus de su nodo predecesor se establecerá en -1. Establezca el waitStatus del nodo principal en 0 a través de cas. Si la configuración falla, significa que el valor de waitStatus anterior ha cambiado. Si la configuración es correcta, se activará el primer nodo de la cola.

Después de que se despierte el primer nodo de la cola:

private void setHeadAndPropagate(Node node, int propagate) {
        Node h = head; // Record old head for check below
        setHead(node);
        if (propagate > 0 || h == null || h.waitStatus < 0 ||
            (h = head) == null || h.waitStatus < 0) {
            Node s = node.next;
            if (s == null || s.isShared())
                doReleaseShared();
        }
    }

Se puede ver que el nodo despertado se configurará a sí mismo como el nodo principal y luego llamará a doReleaseShared para despertar el siguiente nodo. De esta manera, estos dos métodos se llaman cíclicamente y todos los hilos eventualmente se liberarán.

Luego, hable sobre el método doReleaseShared anterior para analizar por qué cas fallará si el nodo principal waitStatus se establece en 0. Porque el hilo que despierta el siguiente nodo y el hilo actual se ejecutan al mismo tiempo. Supongamos aquí que el subproceso actual t0 establece con éxito el nodo principal waitStatus en 0 por primera vez en el subproceso actual t0, luego despierta el subproceso t1, después de que t1 se despierta, establece su propio nodo para encabezar, t0 se ejecuta ah == head, que es falso, porque se reemplaza por t1, así que vuelve a realizar el ciclo. Este método también será llamado por t1, que es el nodo cuya cabeza es t1. Si tanto t0 como t1 se ejecutan hasta que el waitStatus del cabezal se establece en 0, solo un subproceso tiene éxito.

2. ws == 0, significa que es el último nodo, porque el valor de ws lo establece su nodo sucesor. Si cas falla, significa que se ha unido un nuevo nodo y ws se establece en -1, luego para volver a activar el nodo recién agregado. Si tiene éxito,

3. Si h == head es verdadero, significa que el subproceso despertado no se ha establecido como el encabezado del nodo principal, y el subproceso despertador activa la línea de código y sale del bucle. Aunque el subproceso despertado sale, el subproceso despertado posteriormente seguirá ejecutando el método doReleaseShared y el subproceso subsiguiente todavía se despertará.

para resumir

En pocas palabras, cuando las llamadas de un hilo esperan, si el valor del estado no es 0, el hilo está bloqueado. Si countDown reduce el estado a 0, el subproceso que se reduce a 0 es responsable de despertar el primer nodo de la cola de bloqueo. Después de que el nodo se despierte, despertará a sus nodos sucesores, y así sucesivamente, todos los subprocesos bloqueados se despiertan.

Supongo que te gusta

Origin blog.csdn.net/sinat_33472737/article/details/114969159
Recomendado
Clasificación