Как используется CountDownLatch в многопоточности Java?
может кто-нибудь помочь мне понять, что такое Java CountDownLatch и когда его использовать?
у меня нет очень четкого представления о том, как работает эта программа. Как я понимаю, все три потока начинаются сразу, и каждый поток будет вызывать CountDownLatch после 3000ms. So обратный отсчет будет уменьшаться один за другим. После того, как защелка становится нулевой программа печатает "завершено". Может быть, я понял неправильно.
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.");
}
}
10 ответов:
Да, вы правильно поняли.
CountDownLatchработает в принципе защелки, основной поток будет ждать, пока ворота не откроются. Один поток ждет n потоки, указанные при созданииCountDownLatch.любой поток, обычно основной поток приложения, который вызывает
CountDownLatch.await()будет ждать, пока счетчик дойдет до нуля, или он будет прерван другим потоком. Все остальные потоки необходимо отсчитывать, вызываяCountDownLatch.countDown()как только они будут завершены или готовы.как только счетчик достигает нуля, ожидающий поток продолжается. Один из недостатков / преимуществ
CountDownLatchЭто не многоразовый: как только количество достигает нуля, вы не можете использоватьCountDownLatchбольше.Edit:
использовать
CountDownLatchкогда один поток (например, основной поток) требует дождаться завершения одного или нескольких потоков, прежде чем он сможет продолжить обработку.классический пример использования
CountDownLatchв Java-это серверная сторона основное приложение Java, которое использует архитектуру служб, где несколько служб предоставляются несколькими потоками, и приложение не может начать обработку, пока все службы не будут успешно запущены.С. П. Вопрос OP имеет довольно простой пример, поэтому я его не включил.
CountDownLatchв Java есть тип синхронизатора, который позволяет одинThreadдождаться одного или несколькихThreads Перед началом обработки.
CountDownLatchработает по принципу защелки, поток будет ждать, пока ворота открыты. Один поток ждетnколичество потоков, указанных при созданииCountDownLatch.например
final CountDownLatch latch = new CountDownLatch(3);здесь мы устанавливаем счетчик на 3.
любой поток, обычно основной поток приложения, который вызывает
CountDownLatch.await()будет подождите, пока счетчик не достигнет нуля или он будет прерван другимThread. Все остальные потоки должны сделать обратный отсчет, вызвавCountDownLatch.countDown()как только они будут завершены или готовы к работе. как только счет достигает нуля, тоThreadв ожидании запуска.здесь количество уменьшается на
CountDownLatch.countDown()метод.The
Threadчто называетawait()метод будет ждать, пока начальное количество не достигнет нуля.для того чтобы сделать отсчет нул другим потокам нужно чтобы вызвать
countDown()метод. Как только счетчик станет нулевым поток, который вызвалawait()метод возобновится (начнется его выполнение).недостаток
CountDownLatchЭто то, что он не используется повторно: как только счетчик становится нулевым, он больше не используется.
NikolaB объяснил это очень хорошо, однако пример было бы полезно понять, так что вот один простой пример...
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...."); } }
Он используется, когда мы хотим дождаться более чем одного потока для выполнения своей задачи. Это похоже на объединение в потоки.
где мы можем использовать CountDownLatch
рассмотрим сценарий, в котором у нас есть требование, где у нас есть три потока "A", "B" и "C", и мы хотим запустить поток "C" только тогда, когда потоки "A" и "B" завершают или частично завершают свою задачу.
его можно приложить к реальному миру оно сценарий
рассмотрим сценарий, в котором менеджер разделил модули между командами разработки (A и B), и он хочет назначить его команде QA для тестирования только тогда, когда обе команды завершат свою задачу.
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"); } }вывод вышеуказанного кода будет:
задача, поставленная перед командой разработчиков devB
задача, поставленная перед командой разработчиков devA
задача выполнена командой разработчиков devB
задача завершена команда разработчиков Дева
задача, назначенная команде QA
задача выполнена командой QA
здесь ждут() метод ждет, пока флаг countdownlatch станет 0, и обратный отсчет() метод уменьшает флаг обратного отсчета на 1.
ограничение соединения: Приведенный выше пример также может быть достигнут с помощью JOIN, но JOIN не может использоваться в двух сценариях:
- когда мы используем ExecutorService вместо потока класс для создания потоков.
- изменить приведенный выше пример, где менеджер хочет передать код команде QA, как только разработка завершит свою задачу 80%. Это означает, что CountDownLatch позволяют нам изменять реализацию, которая может быть использована для ожидания другого потока для их частичного выполнения.
один хороший пример того, когда использовать что-то вроде этого с Java Simple Serial Connector, доступ к последовательным портам. Обычно вы будете писать что-то в порт, и асинхронно, в другом потоке, устройство будет отвечать на SerialPortEventListener. Как правило, вы хотите сделать паузу после записи в порт, чтобы дождаться ответа. Обработка блокировок потоков для этого сценария вручную чрезвычайно сложна, но использование Countdownlatch легко. Прежде чем вы идете думать, что вы можете сделать это в другой кстати, будьте осторожны с условиями гонки, о которых вы никогда не думали!!
псевдокод:
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(); } } }
CoundDownLatch позволяет заставить поток ждать, пока все другие потоки не будут выполнены с их выполнением.
псевдокод может быть:
// 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
Если вы добавляете некоторую отладку после вашего вызова для фиксации.обратный отсчет (), это может помочь вам лучше понять его поведение.
latch.countDown(); System.out.println("DONE "+this.latch); // Add this debugна выходе будет показано, что количество уменьшается. Этот "счетчик" фактически является количеством выполняемых задач (объектов процессора), которые вы запустили, против которых countDown () имеет не был вызван и, следовательно, блокируется основной поток при его вызове на защелку.ждать.)(
DONE java.util.concurrent.CountDownLatch@70e69696[Count = 2] DONE java.util.concurrent.CountDownLatch@70e69696[Count = 1] DONE java.util.concurrent.CountDownLatch@70e69696[Count = 0]
из документации oracle о CountDownLatch за:
средство синхронизации, которое позволяет одному или нескольким потокам ожидать завершения набора операций, выполняемых в других потоках.
A
CountDownLatchинициализируется с заданным количеством. Элементawaitметоды блокируются до тех пор, пока текущее количество не достигнет нуля из-за вызововcountDown()метод, после которого освобождаются все ожидающие потоки и любые последующие вызовы конечно жду возвращения сразу. Это одноразовое явление-счетчик не может быть сброшен.обратный отсчет является универсальным инструментом синхронизации и может быть использован для ряда целей.
A
CountDownLatchинициализированный с подсчетом одного служит простой защелкой включения/выключения или затвором: все потоки, вызывающие ожидание, ждут у затвора, пока он не будет открыт потоком, вызывающим обратный отсчет().A
CountDownLatchинициализируется N может быть использован, чтобы сделать один нить подождать до n потоков выполнили некоторое действие, или некоторые действия были завершены в N раз.public void await() throws InterruptedExceptionзаставляет текущий поток ждать, пока защелка не будет отсчитана до нуля, если поток не будет прерван.
если текущий счетчик равен нулю, то этот метод возвращает немедленно.
public void countDown()уменьшает количество защелки, освобождая все ожидающие потоки, если количество достигает нуль.
если текущий счет больше нуля, то он уменьшается. Если новое число равно нулю, то все ожидающие потоки снова включаются для целей планирования потоков.
объяснение вашего примера.
вы установили счетчик как 3 для
latchпеременнаяCountDownLatch latch = new CountDownLatch(3);вы прошли этот общий
latchв рабочий поток:Processor- три
Runnableэкземпляров изProcessorбыли представленыExecutorServiceexecutorосновной поток (
App) ждет, когда счетчик станет нулевым с приведенным ниже утверждениемlatch.await();Processorпоток спит в течение 3 секунд, а затем он уменьшает значение счетчика сlatch.countDown()первый
Processэкземпляр изменит количество защелок как 2 после его завершения из-заlatch.countDown().второй
Processэкземпляр изменит защелку считаются 1 после его завершения из-заlatch.countDown().третий
Processэкземпляр изменит количество защелок как 0 после его завершения из-заlatch.countDown().нулевой счет на защелке вызывает основной поток
Appвышли изawaitпрограмма приложения печатает этот вывод сейчас:
Completed
как упоминалось в JavaDoc (https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/CountDownLatch.html), CountDownLatch-это средство синхронизации, введенное в Java 5. Здесь синхронизация не означает ограничение доступа к критическому разделу. Но скорее последовательность действий разных потоков. Тип синхронизации, достигаемый с помощью CountDownLatch, аналогичен типу Join. Предположим, что существует поток "M", который должен ждать другие рабочие потоки "T1"," T2"," T3 " для выполнения своих задач До Java 1.5 это можно сделать следующим образом: M запускает следующий код
T1.join(); T2.join(); T3.join();приведенный выше код гарантирует, что поток M возобновит свою работу после того, как T1, T2, T3 завершит свою работу. T1, T2, T3 могут выполнять свою работу в любом порядке. То же самое может быть достигнуто через CountDownLatch, где T1,T2, T3 и поток M совместно используют один и тот же объект CountDownLatch.
"М" просит :countDownLatch.await();
где как "T1","T2", " T3 " делаетcountDownLatch.countdown();один недостаток метода join заключается в том, что M должен знать о T1, T2, T3. Если есть новый рабочий поток T4, добавленный позже, то M также должен знать об этом. Этого можно избежать с помощью CountDownLatch. После реализации последовательность действий будет [T1,T2,T3](порядок T1,T2, T3 может быть в любом случае) -> [M]
лучший пример в реальном времени для countDownLatch объясняется в этой ссылке CountDownLatchExample
Comments