Simultaneidad de Java: cierre de cuenta atrás vs barrera cíclica

160

Estaba leyendo la API java.util.concurrent y descubrí que

  • CountDownLatch: Una ayuda de sincronización que permite que uno o más subprocesos esperen hasta que se complete un conjunto de operaciones que se realizan en otros subprocesos.
  • CyclicBarrier: Una ayuda de sincronización que permite que un conjunto de subprocesos esperen entre sí para llegar a un punto de barrera común.

Para mí, ambos parecen iguales, pero estoy seguro de que hay mucho más.

Por ejemplo, en CoundownLatch, the countdown value could not be reset, that can happen in the case of CyclicBarrier.

¿Hay alguna otra diferencia entre los dos?
¿En qué use caseslugar querría alguien restablecer el valor de la cuenta regresiva?

soñador
fuente
12
Los cierres son para esperar eventos; Las barreras son para esperar otros hilos. - Java Concurrency in Practice, B.Goetz et al.
user2418306

Respuestas:

137

Una diferencia importante es que CyclicBarrier toma una tarea Ejecutable (opcional) que se ejecuta una vez que se cumple la condición de barrera común.

También le permite obtener el número de clientes que esperan en la barrera y el número requerido para activar la barrera. Una vez activada, la barrera se restablece y se puede volver a usar.

Para casos de uso simples: servicios que comienzan, etc., CountdownLatch está bien. Un CyclicBarrier es útil para tareas de coordinación más complejas. Un ejemplo de tal cosa sería la computación paralela, donde múltiples subtareas están involucradas en la computación, algo así como MapReduce .

Jon
fuente
66
"También le permite obtener el número de clientes que esperan en la barrera y el número requerido para activar la barrera. Una vez activada, la barrera se restablece y se puede volver a usar". Realmente me gusta este punto. Un par de artículos que he leído sugieren que CyclicBarrier es cíclico porque invoca el método reset (). Eso es cierto, pero lo que no suelen mencionar es que la barrera se restablece automáticamente tan pronto como se activa. Publicaré un código de muestra para ilustrar esto.
Kevin Lee
@Kevin Lee Gracias por "la barrera se restablece automáticamente tan pronto como se activa". así que no es necesario llamar a reset () en el código.
supernova el
134

Hay otra diferencia

Cuando se utiliza a CyclicBarrier, se supone que especifica el número de subprocesos en espera que activan la barrera. Si especifica 5, debe tener al menos 5 hilos para llamar await().

Cuando se utiliza a CountDownLatch, se especifica la cantidad de llamadas countDown()que dará como resultado la liberación de todos los hilos en espera. Esto significa que puede usar CountDownLatchun solo hilo.

"¿Por qué harías eso?", Puedes decir. Imagine que está utilizando una API misteriosa codificada por otra persona que realiza devoluciones de llamada. Desea que uno de sus hilos espere hasta que se haya llamado una cierta devolución de llamada varias veces. No tiene idea de qué hilos se llamará a la devolución de llamada. En este caso, a CountDownLatches perfecto, mientras que no se me ocurre ninguna forma de implementar esto usando a CyclicBarrier(en realidad, puedo, pero implica tiempos de espera ... ¡qué asco!).

¡Solo deseo que se CountDownLatchpueda restablecer!

Kim
fuente
10
Creo que esta es la respuesta que mejor muestra las diferencias teóricas. El hecho de que los pestillos se pueden romper simplemente llamando a un método varias veces mientras que las barreras necesitan una cantidad precisa de hilos para esperar ().
flagg19
43
Correcto, esa es la principal diferencia: CountDownLatch -> NumberOfCalls, CyclicBarrier -> NumberOfThreads
Ivan Voroshilin
1
Estoy de acuerdo en que sería genial CountDownLatchpoder reiniciarse: una solución alternativa que uso para implementar una notificación de espera aproximada es simplemente renovar CountDownLatchinmediatamente cuando se ingresa el bloque de código protegido (cuando el pestillo llega a cero). Esto no es aplicable en todas las circunstancias / ámbitos, por supuesto, pero pensé que vale la pena señalar que es una opción en situaciones de Ricitos de Oro.
Ephemera
2
Una de las mejores respuestas sobre este tema. Java Concurrency in Practice- dice lo mismo: Latches are for waiting for events; barriers are for waiting for other threads.. Un punto primario y esencial para entender la diferencia entre estos dos.
Rahul Dev Mishra
El documento de Java 8 dice "Un CountDownLatch inicializado en N puede usarse para hacer que un hilo espere hasta que N hilos hayan completado alguna acción, o alguna acción se haya completado N veces". me parece: CountDownLatch -> NumberOfCalls o CountDownLatch -> NumberOfThreads
nir
41

Un punto que nadie ha mencionado aún es que, en un CyclicBarriercaso, si un hilo tiene un problema (tiempo de espera, interrumpido ...), todos los demás que han llegado await()obtienen una excepción. Ver Javadoc:

CyclicBarrier utiliza un modelo de rotura de todo o nada para los intentos fallidos de sincronización: si un hilo deja un punto de barrera prematuramente debido a la interrupción, falla o tiempo de espera, todos los otros hilos que esperan en ese punto de barrera también saldrán anormalmente a través de BrokenBarrierException (o InterruptedException) si ellos también fueron interrumpidos aproximadamente al mismo tiempo).

Chirlo
fuente
22

Creo que JavaDoc ha explicado las diferencias explícitamente. La mayoría de las personas saben que CountDownLatch no se puede restablecer, sin embargo, CyclicBarrier sí. Pero esta no es la única diferencia, o el CyclicBarrier podría renombrarse a ResetbleCountDownLatch. Deberíamos distinguir las diferencias desde la perspectiva de sus objetivos, que se describen en JavaDoc

CountDownLatch: una ayuda de sincronización que permite que uno o más subprocesos esperen hasta que se complete un conjunto de operaciones que se realizan en otros subprocesos.

CyclicBarrier: una ayuda de sincronización que permite que un conjunto de subprocesos esperen entre sí para llegar a un punto de barrera común.

En countDownLatch, hay uno o más subprocesos que esperan que se complete un conjunto de otros subprocesos . En esta situación, hay dos tipos de subprocesos, un tipo está esperando, otro tipo está haciendo algo, después de finalizar sus tareas, podrían estar esperando o simplemente terminados.

En CyclicBarrier, solo hay un tipo de subprocesos, se están esperando, son iguales.

Justin Civi
fuente
1
"En CyclicBarrier, solo hay un tipo de hilos" ... Son iguales en su "rol de espera" hasta que otros hilos llamen .await (), pero pueden ser "no iguales en lo que hacen". Además, todos deben ser instancias de subprocesos absolutamente diferentes (!) Del mismo tipo o de diferentes tipos, mientras que en CountDownLatch el mismo subproceso puede llamar a countDown () e influir en el resultado.
Vladimir Nabokov
Estoy de acuerdo en que CountDownLatch requiere inherentemente dos roles: un cliente para countDown y un cliente para esperar. Por otro lado, los clientes de CyclicBarrier pueden funcionar bien con el método de espera.
isaolmez
14

La principal diferencia se documenta directamente en los Javadocs para CountdownLatch. A saber:

Un CountDownLatch se inicializa con un recuento dado. Los métodos de espera se bloquean hasta que el recuento actual llega a cero debido a las invocaciones del método countDown (), después de lo cual se liberan todos los hilos de espera y cualquier invocación posterior de espera regresa de inmediato. Este es un fenómeno de una sola vez: el recuento no se puede restablecer. Si necesita una versión que restablezca el recuento, considere usar un CyclicBarrier.

fuente 1.6 Javadoc

JohnnyO
fuente
44
Si su diferencia es simplemente se puede restablecer o no, CyclicBarrier podría llamarse mejor ResetableCountDownLatch, que es más significativo debido a la diferencia.
James.Xu
12

Se utiliza un CountDownLatch para la sincronización de una sola vez. Mientras usa un CountDownLatch, cualquier hilo puede llamar a countDown () tantas veces como quiera. Los subprocesos que llamaron a waitit () se bloquean hasta que el recuento llega a cero debido a llamadas a countDown () por otros subprocesos desbloqueados. El javadoc para CountDownLatch dice:

Los métodos de espera se bloquean hasta que el recuento actual llega a cero debido a las invocaciones del método countDown (), después de lo cual se liberan todos los hilos de espera y cualquier invocación posterior de espera regresa de inmediato. ...

Otro uso típico sería dividir un problema en N partes, describir cada parte con un Runnable que ejecute esa parte y haga una cuenta regresiva en el pestillo, y ponga en cola todos los Runnables a un Ejecutor. Cuando todas las subpartes estén completas, el hilo de coordinación podrá pasar a la espera. (Cuando los subprocesos deben realizar una cuenta regresiva repetida de esta manera, en su lugar use un CyclicBarrier).

Por el contrario, la barrera cíclica se utiliza para múltiples puntos de sincronización, por ejemplo, si un conjunto de subprocesos ejecuta un cálculo en bucle / en fase y necesita sincronizarse antes de comenzar la siguiente iteración / fase. Según el javadoc para CyclicBarrier :

La barrera se llama cíclica porque puede reutilizarse después de que se liberan los hilos en espera.

A diferencia de CountDownLatch, cada llamada a await () pertenece a alguna fase y puede hacer que el hilo se bloquee hasta que todas las partes que pertenecen a esa fase hayan invocado a wait (). No hay una operación explícita countDown () compatible con CyclicBarrier.

farsa
fuente
12

Esta pregunta ya ha sido respondida adecuadamente, pero creo que puedo agregar un poco de valor al publicar un código.

Para ilustrar el comportamiento de la barrera cíclica, hice un código de muestra. Tan pronto como se inclina la barrera, se restablece automáticamente para que pueda usarse nuevamente (por lo tanto, es "cíclica"). Cuando ejecute el programa, observe que las impresiones "Vamos a jugar" se activan solo después de que se inclina la barrera.

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

public class CyclicBarrierCycles {

    static CyclicBarrier barrier;

    public static void main(String[] args) throws InterruptedException {
        barrier = new CyclicBarrier(3); 

        new Worker().start();
        Thread.sleep(1000);
        new Worker().start();
        Thread.sleep(1000);
        new Worker().start();
        Thread.sleep(1000);

        System.out.println("Barrier automatically resets.");

        new Worker().start();
        Thread.sleep(1000);
        new Worker().start();
        Thread.sleep(1000);
        new Worker().start();
    }

}


class Worker extends Thread {
    @Override
    public void run() {
        try {
            CyclicBarrierCycles.barrier.await();
            System.out.println("Let's play.");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (BrokenBarrierException e) {
            e.printStackTrace();
        }
    }
}
Kevin Lee
fuente
8

Cuando estudiaba sobre pestillos y barreras cíclicas, se me ocurrió esta metáfora. barreras cíclicas : imagine que una empresa tiene una sala de reuniones. Para comenzar la reunión, un cierto número de asistentes a la reunión tienen que venir a la reunión (para que sea oficial). el siguiente es el código de un asistente de reunión normal (un empleado)

class MeetingAtendee implements Runnable {

CyclicBarrier myMeetingQuorumBarrier;

public MeetingAtendee(CyclicBarrier myMileStoneBarrier) {
    this.myMeetingQuorumBarrier = myMileStoneBarrier;
}

@Override
public void run() {
    try {
        System.out.println(Thread.currentThread().getName() + " i joined the meeting ...");
        myMeetingQuorumBarrier.await();
        System.out.println(Thread.currentThread().getName()+" finally meeting stared ...");
    } catch (InterruptedException e) {
        e.printStackTrace();
    } catch (BrokenBarrierException e) {
        System.out.println("Meeting canceled! every body dance <by chic band!>");
    }
 }
}

el empleado se une a la reunión, espera a que otros vengan para comenzar a reunirse. también se sale si la reunión se cancela :) entonces tenemos THE BOSS cómo a las dosis no les gusta esperar a que otros se presenten y si pierde a su paciente, cancela la reunión.

class MeetingAtendeeTheBoss implements Runnable {

CyclicBarrier myMeetingQuorumBarrier;

public MeetingAtendeeTheBoss(CyclicBarrier myMileStoneBarrier) {
    this.myMeetingQuorumBarrier = myMileStoneBarrier;
}

@Override
public void run() {
    try {
        System.out.println(Thread.currentThread().getName() + "I am THE BOSS - i joined the meeting ...");
        //boss dose not like to wait too much!! he/she waits for 2 seconds and we END the meeting
        myMeetingQuorumBarrier.await(1,TimeUnit.SECONDS);
        System.out.println(Thread.currentThread().getName()+" finally meeting stared ...");
    } catch (InterruptedException e) {
        e.printStackTrace();
    } catch (BrokenBarrierException e) {
        System.out.println("what WHO canceled The meeting");
    } catch (TimeoutException e) {
        System.out.println("These employees waste my time!!");
    }
 }
}

En un día normal, los empleados vienen a la reunión y esperan que otros se presenten, y si algunos de los asistentes no vienen, ¡tienen que esperar indefinidamente! en alguna reunión especial, el jefe viene y no le gusta esperar (5 personas deben comenzar a reunirse, pero solo viene el jefe y también un empleado entusiasta), por lo que cancela la reunión (enojado)

CyclicBarrier meetingAtendeeQuorum = new CyclicBarrier(5);
Thread atendeeThread = new Thread(new MeetingAtendee(meetingAtendeeQuorum));
Thread atendeeThreadBoss = new Thread(new MeetingAtendeeTheBoss(meetingAtendeeQuorum));
    atendeeThread.start();
    atendeeThreadBoss.start();

Salida:

//Thread-1I am THE BOSS - i joined the meeting ...
// Thread-0 i joined the meeting ...
// These employees waste my time!!
// Meeting canceled! every body dance <by chic band!>

Hay otro escenario en el que otro hilo externo (un terremoto) cancela la reunión (método de restablecimiento de llamada). en este caso, todos los hilos de espera se despiertan por una excepción.

class NaturalDisasters implements Runnable {

CyclicBarrier someStupidMeetingAtendeeQuorum;

public NaturalDisasters(CyclicBarrier someStupidMeetingAtendeeQuorum) {
    this.someStupidMeetingAtendeeQuorum = someStupidMeetingAtendeeQuorum;
}

void earthQuakeHappening(){
    System.out.println("earth quaking.....");
    someStupidMeetingAtendeeQuorum.reset();
}

@Override
public void run() {
    earthQuakeHappening();
 }
}

ejecutar código dará como resultado una salida divertida:

// Thread-1I am THE BOSS - i joined the meeting ...
// Thread-0 i joined the meeting ...
// earth quaking.....
// what WHO canceled The meeting
// Meeting canceled! every body dance <by chic band!>

También puede agregar una secretaria a la sala de reuniones, si se celebra una reunión, documentará todo pero no formará parte de la reunión:

class MeetingSecretary implements Runnable {

@Override
public void run() {
        System.out.println("preparing meeting documents");
        System.out.println("taking notes ...");
 }
}

Cierres : si el jefe enojado quiere organizar una exhibición para los clientes de la compañía, todo debe estar listo (recursos). proporcionamos una lista de tareas pendientes a cada trabajador (Hilo) que dosifica su trabajo y verificamos la lista de tareas pendientes (algunos trabajadores pintan, otros preparan el sistema de sonido ...). Cuando todos los elementos de la lista de tareas están completos (se proporcionan recursos), podemos abrir las puertas a los clientes.

public class Visitor implements Runnable{

CountDownLatch exhibitonDoorlatch = null;

public Visitor (CountDownLatch latch) {
    exhibitonDoorlatch  = latch;
}

public void run() {
    try {
        exhibitonDoorlatch .await();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }

    System.out.println("customer visiting exebition");
 }
}

Y los trabajadores cómo están preparando la exposición:

class Worker implements Runnable {

CountDownLatch myTodoItem = null;

public Worker(CountDownLatch latch) {
    this.myTodoItem = latch;
}

public void run() {
        System.out.println("doing my part of job ...");
        System.out.println("My work is done! remove it from todo list");
        myTodoItem.countDown();
 }
}

    CountDownLatch preperationTodoList = new CountDownLatch(3);

    // exhibition preparation workers  
    Worker      electricalWorker      = new Worker(preperationTodoList);
    Worker      paintingWorker      = new Worker(preperationTodoList);

    // Exhibition Visitors 
    ExhibitionVisitor exhibitionVisitorA = new ExhibitionVisitor(preperationTodoList);
    ExhibitionVisitor exhibitionVisitorB = new ExhibitionVisitor(preperationTodoList);
    ExhibitionVisitor exhibitionVisitorC = new ExhibitionVisitor(preperationTodoList);

    new Thread(electricalWorker).start();
    new Thread(paintingWorker).start();

    new Thread(exhibitionVisitorA).start();
    new Thread(exhibitionVisitorB).start();
    new Thread(exhibitionVisitorC).start();
Mr.Q
fuente
6

En pocas palabras , solo para comprender las diferencias funcionales clave entre los dos:

public class CountDownLatch {
    private Object mutex = new Object();
    private int count;

    public CountDownLatch(int count) {
        this.count = count;
    }

    public void await() throws InterruptedException {
        synchronized (mutex) {
            while (count > 0) {
                mutex.wait();
            }
        }
    }

    public void countDown() {
        synchronized (mutex) {
            if (--count == 0)
                mutex.notifyAll();
        }

    }
}

y

public class CyclicBarrier {
    private Object mutex = new Object();
    private int count;

    public CyclicBarrier(int count) {
        this.count = count;
    }

    public void await() throws InterruptedException {
        synchronized (mutex) {
            count--;
            while(count > 0)
                mutex.wait();
            mutex.notifyAll();
        }
    }
}

excepto, por supuesto, características como no bloqueo, espera temporizada, diagnósticos y todo lo que se ha explicado en detalle en las respuestas anteriores.

Sin embargo, las clases anteriores son completamente funcionales y equivalentes, dentro de la funcionalidad proporcionada, a sus homónimos correspondientes.

En una nota diferente, CountDownLatchlas subclases de clase interna AQS, mientras CyclicBarrierusa ReentrantLock(mi sospecha es que podría ser al revés o que ambas podrían usar AQS o ambas usan Lock, sin ninguna pérdida de eficiencia de rendimiento)

igor.zh
fuente
5

Una diferencia obvia es que solo N hilos pueden esperar en un CyclicBarrier of N para ser liberados en un ciclo. Pero puede esperar un número ilimitado de subprocesos en un CountDownLatch de N. El decremento de la cuenta regresiva puede realizarse por un subproceso N veces o N subprocesos una vez cada uno o combinaciones.

Pramma
fuente
4

En el caso de CyclicBarrier, tan pronto como TODOS los hilos secundarios comiencen a llamar a barrera.await (), el Runnable se ejecuta en la Barrera. La barrera. Espere en cada subproceso secundario tomará una duración de tiempo diferente para terminar, y todos terminan al mismo tiempo.

Brandon
fuente
4

En CountDownLatch , los subprocesos principales esperan que otros subprocesos completen su ejecución. En CyclicBarrier , los subprocesos de trabajo esperan entre sí para completar su ejecución.

No puede reutilizar la misma instancia de CountDownLatch una vez que el recuento llega a cero y el pestillo está abierto, por otro lado, CyclicBarrier puede reutilizarse restableciendo la barrera, una vez que se rompe la barrera.

V Jo
fuente
No necesita ser el hilo principal. Puede ser cualquier hilo que cree CountDownLatch y lo comparta con otros hilos no principales.
Aniket Thakur
1

CountDownLatch es una cuenta regresiva de cualquier cosa; CyclicBarrier es una cuenta regresiva solo para hilo

suponga que hay 5 hilos de trabajo y un hilo de remitente, y cuando los trabajadores producen 100 artículos, el remitente los enviará.

Para CountDownLatch, el contador puede estar en trabajadores o artículos

Para CyclicBarrier, el contador solo puede en trabajadores

Si un trabajador se duerme infinitamente, con CountDownLatch en los artículos, Shipper puede enviar; Sin embargo, con CyclicBarrier, Shipper nunca puede ser llamado

yk42b
fuente
0

@Kevin Lee y @Jon Probé CyclicBarrier con Opcional Runnable. Parece que se ejecuta al principio y después de que se inclina el CyclicBarrier. Aquí está el código y la salida

barrera de barrera estática cíclica;

    public static void main(String[] args) throws InterruptedException {
        barrier = new CyclicBarrier(3, new Runnable() {
            @Override
            public void run() {
                System.out.println("I run in the beginning and after the CyclicBarrier is tipped");
            }
        });

        new Worker().start();
        Thread.sleep(1000);
        new Worker().start();
        Thread.sleep(1000);
        new Worker().start();
        Thread.sleep(1000);

        System.out.println("Barrier automatically resets.");

        new Worker().start();
        Thread.sleep(1000);
        new Worker().start();
        Thread.sleep(1000);
        new Worker().start();
    }

Salida

I run in the beginning and after the CyclicBarrier is tipped
Let's play.
Let's play.
Let's play.
Barrier automatically resets.
I run in the beginning and after the CyclicBarrier is tipped
Let's play.
Let's play.
Let's play.
Hari Rao
fuente