¿Cómo se usa CountDownLatch en Java Multithreading?
(10)
Como se menciona en JavaDoc ( docs.oracle.com/javase/7/docs/api/java/util/concurrent/… ), CountDownLatch es un asistente de sincronización, introducido en Java 5. Aquí la sincronización no significa restringir el acceso a una sección crítica. Pero en lugar de secuenciar acciones de diferentes hilos. El tipo de sincronización logrado a través de CountDownLatch es similar al de Join. Suponga que hay un hilo "M" que necesita esperar que otros hilos de trabajador "T1", "T2", "T3" completen sus tareas. Antes de Java 1.5, la forma en que esto se puede hacer es, M ejecutando el siguiente código
T1.join();
T2.join();
T3.join();
El código anterior se asegura de que el hilo M reanude su trabajo después de que T1, T2, T3 completen su trabajo. T1, T2, T3 pueden completar su trabajo en cualquier orden. Lo mismo se puede lograr a través de CountDownLatch, donde T1, T2, T3 e hilo M comparten el mismo objeto CountDownLatch.
Solicitudes "M": countDownLatch.await();
donde como "T1", "T2", "T3" cuenta countDownLatch.countdown();
Una desventaja del método de unión es que M tiene que saber acerca de T1, T2, T3. Si hay un nuevo hilo de trabajo T4 añadido más tarde, entonces M también debe tenerlo en cuenta. Esto se puede evitar con CountDownLatch. Después de la implementación, la secuencia de acción sería [T1, T2, T3] (el orden de T1, T2, T3 podría ser de todos modos) -> [M]
¿Alguien puede ayudarme a entender qué es Java CountDownLatch
y cuándo usarlo?
No tengo una idea muy clara de cómo funciona este programa. Como yo entiendo, los tres hilos comienzan al mismo tiempo y cada Thread llamará a CountDownLatch después de 3000ms. Entonces, la cuenta atrás disminuirá uno por uno. Después de que el cerrojo se vuelva cero, el programa imprime "Completado". Quizás la forma en que entendí es incorrecta.
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
class Processor implements Runnable {
private CountDownLatch latch;
public Processor(CountDownLatch latch) {
this.latch = latch;
}
public void run() {
System.out.println("Started.");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
latch.countDown();
}
}
// ------------------------------------------------ -----
public class App {
public static void main(String[] args) {
CountDownLatch latch = new CountDownLatch(3); // coundown from 3 to 0
ExecutorService executor = Executors.newFixedThreadPool(3); // 3 Threads in pool
for(int i=0; i < 3; i++) {
executor.submit(new Processor(latch)); // ref to latch. each time call new Processes latch will count down by 1
}
try {
latch.await(); // wait until latch counted down to 0
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Completed.");
}
}
CoundDownLatch le permite hacer que un subproceso espere hasta que todos los demás subprocesos se hayan completado con su ejecución.
El pseudo código puede ser:
// Main thread starts
// Create CountDownLatch for N threads
// Create and start N threads
// Main thread waits on latch
// N threads completes there tasks are returns
// Main thread resume execution
De la documentación de CountDownLatch sobre CountDownLatch :
Un asistente de sincronización que permite que uno o más subprocesos esperen hasta que se complete un conjunto de operaciones en otros subprocesos.
Un CountDownLatch
se inicializa con un conteo dado. El bloque de métodos de await
hasta que el recuento actual llega a cero debido a las invocaciones del método countDown()
, después del cual se liberan todos los subprocesos en espera y las invocaciones posteriores de espera vuelven inmediatamente. Este es un fenómeno de una sola vez: el recuento no se puede restablecer.
Un CountDownLatch es una herramienta de sincronización versátil y puede usarse para varios propósitos.
Un CountDownLatch
inicializado con un recuento de uno sirve como un simple enclavamiento on / off, o puerta: todos los hilos que invocan esperan a esperar en la puerta hasta que se abre por un hilo que invoca countDown ().
Un CountDownLatch
inicializado en N se puede usar para hacer que un hilo espere hasta que N hilos hayan completado alguna acción, o alguna acción se haya completado N veces.
public void await()
throws InterruptedException
Hace que el hilo actual espere hasta que el enganche cuente hacia abajo a cero, a menos que el hilo se interrumpa.
Si el conteo actual es cero, este método regresa inmediatamente.
public void countDown()
Disminuye el recuento del pestillo, liberando todos los hilos de espera si el recuento llega a cero.
Si el recuento actual es mayor que cero, entonces se decrementa. Si el nuevo recuento es cero, todos los subprocesos en espera se vuelven a habilitar para fines de programación de subprocesos.
Explicación de tu ejemplo.
Has configurado el recuento como 3 para la variable de
latch
CountDownLatch latch = new CountDownLatch(3);
Ha pasado este
latch
compartido a Hilo de trabajo:Processor
- Se han
Runnable
tres instanciasRunnable
deProcessor
alexecutor
ExecutorService
El hilo principal (
App
) está esperando que la cuenta se vuelva cero con la declaración siguientelatch.await();
-
Processor
hilo delProcessor
duerme durante 3 segundos y luego disminuye el valor del conteo conlatch.countDown()
Process
instancia de FirstProcess
cambiará el recuento de pestillo como 2 después de que se complete debido alatch.countDown()
.La segunda instancia del
Process
cambiará el conteo del enganche como 1 después de su finalización debido alatch.countDown()
.Process
instancia de ThirdProcess
cambiará el recuento de pestillo como 0 después de su finalización debido alatch.countDown()
.La cuenta cero en el pestillo hace que la
App
principal del hilo salga de laawait
El programa de aplicación imprime este resultado ahora:
Completed
El mejor ejemplo de tiempo real para countDownLatch explicado en este enlace CountDownLatchExample
NikolaB lo explicó muy bien, sin embargo, un ejemplo sería útil para entenderlo, así que aquí hay un ejemplo simple ...
import java.util.concurrent.*;
public class CountDownLatchExample {
public static class ProcessThread implements Runnable {
CountDownLatch latch;
long workDuration;
String name;
public ProcessThread(String name, CountDownLatch latch, long duration){
this.name= name;
this.latch = latch;
this.workDuration = duration;
}
public void run() {
try {
System.out.println(name +" Processing Something for "+ workDuration/1000 + " Seconds");
Thread.sleep(workDuration);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name+ "completed its works");
//when task finished.. count down the latch count...
// basically this is same as calling lock object notify(), and object here is latch
latch.countDown();
}
}
public static void main(String[] args) {
// Parent thread creating a latch object
CountDownLatch latch = new CountDownLatch(3);
new Thread(new ProcessThread("Worker1",latch, 2000)).start(); // time in millis.. 2 secs
new Thread(new ProcessThread("Worker2",latch, 6000)).start();//6 secs
new Thread(new ProcessThread("Worker3",latch, 4000)).start();//4 secs
System.out.println("waiting for Children processes to complete....");
try {
//current thread will get notified if all chidren''s are done
// and thread will resume from wait() mode.
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("All Process Completed....");
System.out.println("Parent Thread Resuming work....");
}
}
Sí, entendiste correctamente. CountDownLatch
funciona en principio de cierre, el hilo principal esperará hasta que la compuerta esté abierta. Un subproceso espera n subprocesos, especificados al crear CountDownLatch
.
Cualquier hilo, generalmente el hilo principal de la aplicación, que llama a CountDownLatch.await()
esperará hasta que el contador llegue a cero o se interrumpa por otro hilo. Todos los demás hilos deben contar hacia atrás llamando a CountDownLatch.countDown()
una vez que estén completos o listos.
Tan pronto como el conteo llega a cero, el hilo de espera continúa. Una de las desventajas / ventajas de CountDownLatch
es que no es reutilizable: una vez que el conteo llega a cero no puede usar CountDownLatch
.
Editar:
Use CountDownLatch
cuando un hilo (como el hilo principal) requiere que espere uno o más hilos para completar, antes de que pueda continuar el proceso.
Un ejemplo clásico del uso de CountDownLatch
en Java es una aplicación Java central del lado del servidor que utiliza la arquitectura de servicios, donde múltiples hilos son provistos por múltiples hilos y la aplicación no puede comenzar a procesar hasta que todos los servicios hayan comenzado con éxito.
La pregunta de PS OP tiene un ejemplo bastante sencillo, así que no incluí una.
Se usa cuando queremos esperar a que más de un hilo complete su tarea. Es similar a unirse en hilos.
Donde podemos usar CountDownLatch
Considere un escenario donde tenemos un requisito donde tenemos tres hilos "A", "B" y "C" y queremos comenzar el hilo "C" solo cuando los hilos "A" y "B" completan o completan parcialmente su tarea.
Se puede aplicar al escenario de TI del mundo real
Considere un escenario en el que el administrador divide los módulos entre los equipos de desarrollo (A y B) y desea asignarlo al equipo de control de calidad para que lo pruebe solo cuando ambos equipos completen su tarea.
public class Manager {
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(2);
MyDevTeam teamDevA = new MyDevTeam(countDownLatch, "devA");
MyDevTeam teamDevB = new MyDevTeam(countDownLatch, "devB");
teamDevA.start();
teamDevB.start();
countDownLatch.await();
MyQATeam qa = new MyQATeam();
qa.start();
}
}
class MyDevTeam extends Thread {
CountDownLatch countDownLatch;
public MyDevTeam (CountDownLatch countDownLatch, String name) {
super(name);
this.countDownLatch = countDownLatch;
}
@Override
public void run() {
System.out.println("Task assigned to development team " + Thread.currentThread().getName());
try {
Thread.sleep(2000);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
System.out.println("Task finished by development team Thread.currentThread().getName());
this.countDownLatch.countDown();
}
}
class MyQATeam extends Thread {
@Override
public void run() {
System.out.println("Task assigned to QA team");
try {
Thread.sleep(2000);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
System.out.println("Task finished by QA team");
}
}
La salida del código anterior será:
Tarea asignada al equipo de desarrollo devB
Tarea asignada al equipo de desarrollo devA
Tarea terminada por el equipo de desarrollo devB
Tarea terminada por el equipo de desarrollo devA
Tarea asignada al equipo de control de calidad
Tarea terminada por el equipo de QA
Aquí el método await () espera a que el indicador countdownlatch se convierta en 0, y el método countDown () disminuye el indicador countdownlatch en 1.
Limitación de JOIN: El ejemplo anterior también se puede lograr con JOIN, pero JOIN no se puede usar en dos escenarios:
- Cuando utilizamos ExecutorService en lugar de la clase Thread para crear subprocesos.
- Modifique el ejemplo anterior donde el administrador desea transferir el código al equipo de control de calidad tan pronto como el desarrollo complete su tarea del 80%. Significa que CountDownLatch nos permite modificar la implementación que puede usarse para esperar otro hilo para su ejecución parcial.
Si agrega un poco de depuración después de su llamada a latch.countDown (), esto puede ayudarlo a comprender mejor su comportamiento.
latch.countDown();
System.out.println("DONE "+this.latch); // Add this debug
La salida mostrará que el recuento está decrementado. Este ''recuento'' es efectivamente el número de tareas Ejecutables (objetos del procesador) que ha iniciado contra el que countDown () no se ha invocado y, por lo tanto, está bloqueado el hilo principal en su llamada a latch.await ().
DONE java.util.concurrent.CountDownLatch@70e69696[Count = 2]
DONE java.util.concurrent.CountDownLatch@70e69696[Count = 1]
DONE java.util.concurrent.CountDownLatch@70e69696[Count = 0]
Un buen ejemplo de cuándo usar algo como esto es con Java Simple Serial Connector, accediendo a puertos seriales. Por lo general, escribirás algo en el puerto, y de manera asincrónica, en otro hilo, el dispositivo responderá en un SerialPortEventListener. Normalmente, querrá pausar después de escribir en el puerto para esperar la respuesta. Manejar manualmente los bloqueos de subprocesos para este escenario es extremadamente complicado, pero usar Countdownlatch es fácil. Antes de ir pensando que puedes hacerlo de otra manera, ¡ten cuidado con las condiciones de carrera en las que nunca pensaste!
Pseudocódigo:
CountDownLatch latch;
void writeData() {
latch = new CountDownLatch(1);
serialPort.writeBytes(sb.toString().getBytes())
try {
latch.await(4, TimeUnit.SECONDS);
} catch (InterruptedException e) {
}
}
class SerialPortReader implements SerialPortEventListener {
public void serialEvent(SerialPortEvent event) {
if(event.isRXCHAR()){//If data is available
byte buffer[] = serialPort.readBytes(event.getEventValue());
latch.countDown();
}
}
}
CountDownLatch
en Java es un tipo de sincronizador que permite que un Thread
espere uno o más Thread
antes de que comience el procesamiento.
CountDownLatch
funciona según el principio de enganche, el hilo esperará hasta que la puerta esté abierta. Un subproceso espera n
cantidad de subprocesos especificados al crear CountDownLatch
.
por ejemplo, final CountDownLatch latch = new CountDownLatch(3);
Aquí establecemos el contador a 3.
Cualquier hilo, generalmente el hilo principal de la aplicación, que llama a CountDownLatch.await()
esperará hasta que el contador llegue a cero o se interrumpa por otro Thread
. Todos los demás hilos deben hacer una cuenta regresiva llamando a CountDownLatch.countDown()
una vez que estén completos o listos para el trabajo. tan pronto como el conteo llega a cero, el Thread
espera comienza a ejecutarse.
Aquí el recuento se reduce por el método CountDownLatch.countDown()
.
El Thread
que llama al método await()
esperará hasta que el recuento inicial llegue a cero.
Para hacer el recuento cero, otros subprocesos necesitan llamar al método countDown()
. Una vez que el conteo llega a cero, el hilo que invocó el método await()
se reanudará (comenzará su ejecución).
La desventaja de CountDownLatch
es que no es reutilizable: una vez que el recuento se vuelve cero ya no se puede usar.