2010-11-12 20 views
126

estaba leyendo a través de la java.util.concurrent API, y encontró queJava concurrencia: Cuenta atrás pestillo vs barrera cíclica

  • CountDownLatch: Una ayuda de sincronización que permite a uno o más hilos que esperar hasta que un conjunto de operaciones que se realiza en otros hilos completa.
  • CyclicBarrier: Ayuda de sincronización que permite que un conjunto de hilos esperen unos a otros para alcanzar 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?
¿Cuáles son los use cases donde alguien querría restablecer el valor de la cuenta regresiva?

+4

Cierres son para la espera de los acontecimientos; las barreras son para esperar otros hilos. - Java Concurrency in Practice, B.Goetz et al. – user2418306

Respuesta

103

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

También le permite obtener la cantidad de clientes esperando en la barrera y el número requerido para activar la barrera. Una vez activada, la barrera se reinicia y puede usarse nuevamente.

Para casos de uso simple - servicios que comienzan etc ... un CountdownLatch está bien. Un CyclicBarrier es útil para tareas de coordinación más complejas. Un ejemplo de esto sería el cómputo paralelo, donde múltiples subtareas están involucradas en el cálculo, algo así como MapReduce.

+3

"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 reinicia y puede usarse nuevamente". Me gusta mucho este punto. Un par de artículos que he leído sugieren que CyclicBarrier es cíclico porque invocas 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. Voy a publicar un código de muestra para ilustrar esto. –

+0

@Kevin Lee Gracias por "la barrera se restablece automáticamente tan pronto como se activa". así que no hay necesidad de llamar a reset() en el código. – supernova

11

La diferencia principal está documentada en los Javadocs de CountdownLatch. A saber:

Un CountDownLatch se inicializa con un recuento dado . Los métodos aguardan bloquean hasta que el recuento actual llega a cero debido a las invocaciones de la cuenta atrás() método, después de lo cual todos los subprocesos en espera se liberan y cualquier posteriores invocaciones de retorno esperar inmediatamente. Este es un fenómeno de un solo disparo - el recuento no puede ser restablecer. Si necesita una versión que restablece el recuento, considere usar CyclicBarrier.

fuente 1.6 Javadoc

+4

Si la diferencia solo se puede restablecer o no, es posible que CyclicBarrier se llame mejor ResetableCountDownLatch, que es más significativo debido a la diferencia. –

99

Hay otra diferencia.

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

Al usar un CountDownLatch, especifique el número de llamadas a countDown() que darán lugar a que se liberen todos los hilos en espera. Esto significa que puede usar un CountDownLatch con solo un hilo.

"¿Por qué harías eso?", Se puede decir. Imagine que está utilizando una API misteriosa codificada por otra persona que realiza devoluciones de llamada.Desea que uno de sus subprocesos espere hasta que se haya llamado a una devolución de llamada determinada varias veces. No tiene idea de a qué subprocesos se llamará la devolución de llamada. En este caso, un CountDownLatch es perfecto, mientras que no puedo pensar en ninguna forma de implementar esto usando un CyclicBarrier (de hecho, puedo, pero implica tiempos de espera ... ¡puaj!).

¡Ojalá se pudiera restablecer CountDownLatch!

+9

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 varias veces un método mientras que las barreras necesitan una cantidad precisa de subprocesos para esperar(). – flagg19

+21

Derecha: esa es la principal diferencia: CountDownLatch -> NumberOfCalls, CyclicBarrier -> NumberOfThreads –

+0

¡Explicación perfecta! –

3

En el caso de CyclicBarrier, tan pronto como TODOS los hilos hijo comiencen a llamar a barrier.await(), Runnable se ejecuta en la Barrera. La barrera que aguarda en cada hilo secundario tendrá una duración diferente para finalizar, y todos terminan al mismo tiempo.

8

Un CountDownLatch se utiliza para la sincronización de una sola vez. Mientras se utiliza un CountDownLatch, cualquier hilo puede llamar a countDown() tantas veces como lo desee. Los subprocesos que llaman await() se bloquean hasta que el conteo llega a cero debido a llamadas a countDown() por otros subprocesos desbloqueados. Los javadoc for CountDownLatch estados:

El await métodos bloquean hasta que el recuento actual llega a cero debido a invocaciones del método de cuenta atrás(), después de lo cual todos los subprocesos en espera se liberan y cualquier invocaciones posteriores de retorno esperar inmediatamente. ...

Otro uso típico sería dividir un problema en N partes, describen cada parte con una Ejecutable que ejecuta esa parte y recuento hacia abajo el pestillo, y hacer cola toda la Runnables a un ejecutor. Cuando todas las subpartes estén completas, el hilo coordinador podrá pasar . (Cuando las roscas deben contar repetidamente en esta manera, en lugar de utilizar un CyclicBarrier.)

En contraste, la barrera cíclico se utiliza para múltiples puntos de Sincronización, por ejemplo, si un conjunto de subprocesos está ejecutando un cálculo en bucle/en fase y necesita sincronizarse antes de comenzar la siguiente iteración/fase. Como por el javadoc for CyclicBarrier:

La barrera se denomina cíclico, ya que puede ser reutilizado después de las subprocesos en espera se liberan.

A diferencia de la CountDownLatch, cada llamada a la espera() pertenece a alguna fase y pueden causar la rosca para bloquear hasta que todas las partes que pertenecen a esa fase han invocado esperar(). No hay una operación explícita de countDown() soportada por CyclicBarrier.

30

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

El CyclicBarrier utiliza un modelo de rotura de todo-o-nada para los intentos de sincronización fallida: Si un hilo deja un punto prematuramente debido a la interrupción, insuficiencia, o tiempo de espera de barrera, todos los otros hilos de espera en ese punto barrera también se irá anormalmente a través de BrokenBarrierException (o InterruptedException si también fueron interrumpidos aproximadamente al mismo tiempo).

9

Ya se ha respondido adecuadamente esta pregunta, pero creo que puedo agregar algo de valor mediante la publicación de algún código.

Para ilustrar el comportamiento de la barrera cíclica, he hecho algunos ejemplos de código. Tan pronto como la barrera se inclina, se restablece automáticamente para que pueda volver a utilizarse (por lo tanto, es "cíclica"). Cuando ejecute el programa, observe que las impresiones "Let's play" se activan solo después de que se da una propina.

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(); 
     } 
    } 
} 
17

Creo que JavaDoc ha explicado las diferencias explícitamente. La mayoría de la gente sabe que CountDownLatch no se puede restablecer; sin embargo, CyclicBarrier sí puede. Pero esta no es la única diferencia, o CyclicBarrier podría renombrarse como ResetbleCountDownLatch. Debemos decir las diferencias desde la perspectiva de sus objetivos, que se describen en JavaDoc

CountDownLatch: Una ayuda de sincronización que permite a uno o más hilos que esperar hasta que un conjunto de operaciones que se realiza en otros hilos completa.

CyclicBarrier: Ayuda de sincronización que permite que un conjunto de hilos esperen unos a otros para alcanzar un punto de barrera común.

En countDownLatch, hay uno o más subprocesos, que esperan un conjunto de más subprocesos para completar. En esta situación, hay dos tipos de subprocesos, un tipo está esperando, otro tipo está haciendo algo, una vez que finaliza sus tareas, puede estar esperando o simplemente finalizado.

En CyclicBarrier, hay un solo tipo de subprocesos, están esperando el uno al otro, son iguales.

+1

"En CyclicBarrier, solo hay un tipo de subprocesos" ... Son iguales en su "función de espera" hasta que otros subprocesos llaman aa(), pero pueden ser "no iguales en lo que hacen". Además, todos deben ser instancias de subprocesos (!) Del mismo tipo o de tipos diferentes, mientras que en CountDownLatch el mismo subproceso puede llamar a countDown() e influir en el resultado. –

3

Una diferencia obvia es que solo pueden esperar N subidas en un CyclicBarrier de N para ser lanzadas en un ciclo. Pero un número ilimitado de subprocesos puede esperar en un CountDownLatch de N. El decremento de la cuenta atrás se puede hacer con un hilo N veces o N hilos una vez cada uno o combinaciones.

0

En CountDownLatch, los subprocesos principales esperan a que otros subprocesos finalicen su ejecución. En CyclicBarrier, los subprocesos de trabajo esperan el uno al otro para completar su ejecución.

No se puede reutilizar misma instancia CountDownLatch vez cuenta llegue a cero y pestillo está abierto, por el contrario CyclicBarrier puede ser reutilizada por restablecer Barrera, Una vez que la barrera se rompe.

3

En pocas palabras, sólo para entender clave funcionales diferencias 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, funciones como no bloqueante, espera temporizada , diagnósticos y todo lo que se ha explicado detalladamente en las respuestas anteriores.

Las clases anteriores son, sin embargo, totalmente funcionales y equivalentes, dentro de la funcionalidad provista, a sus homónimos correspondientes.

En otro orden, clase interna CountDownLatch 's subclases AQS, mientras CyclicBarrier utiliza ReentrantLock (mi sospecha es que podría ser revés o ambos podrían usar AQS o ambos utilización de bloqueo - sin ninguna pérdida de eficiencia de rendimiento)

3

Cuando estaba estudiando sobre cierres y barreras cíclicas, se me ocurrieron estas metáforas. cyclicbarriers: Imagine que una empresa tiene una sala de reuniones. Para comenzar la reunión, un cierto número de asistentes a la reunión deben asistir a la reunión (para hacerlo oficial). El siguiente es el código de un asistente a la 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!>"); 
    } 
} 
} 

empleado entra en la reunión, espera a que otros vengan a comenzar la reunión. también se sale de él si la reunión se cancela :) entonces tenemos a THE BOSS de cómo a las dosis no les gusta esperar a que aparezcan otros 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 satisfacer esperar a que otros a aparecer y si algunos asistentes don `t vienen tienen que esperar indefinidamente! en alguna reunión especial el jefe viene y que no le gusta que esperar (5 personas tienen que empezar a conocer, pero sólo jefe viene y también un empleado entusiasta) por lo que cancela la reunión (enfadado)

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

de 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 con 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(); 
} 
} 

código que se ejecuta permite la 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 lleva a cabo una reunión que va a documentar todas las cosas, pero ella no es 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 realizar una exposición para los clientes de la compañía, cada cosa tiene que estar preparado (recursos). proporcionamos una lista de cosas por hacer cada trabajador (subproceso) dosifica su trabajo y verificamos la lista de tareas pendientes (algunos trabajadores pintan, otros preparan el sistema de sonido ...). cuando todos los elementos en 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 se 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(); 
Cuestiones relacionadas