Как используется 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.");
}

}
641   10  

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 не может использоваться в двух сценариях:

  1. когда мы используем ExecutorService вместо потока класс для создания потоков.
  2. изменить приведенный выше пример, где менеджер хочет передать код команде 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()

уменьшает количество защелки, освобождая все ожидающие потоки, если количество достигает нуль.

если текущий счет больше нуля, то он уменьшается. Если новое число равно нулю, то все ожидающие потоки снова включаются для целей планирования потоков.

объяснение вашего примера.

  1. вы установили счетчик как 3 для latch переменная

    CountDownLatch latch = new CountDownLatch(3);
    
  2. вы прошли этот общий latch в рабочий поток:Processor

  3. три Runnable экземпляров из Processor были представлены ExecutorServiceexecutor
  4. основной поток ( App) ждет, когда счетчик станет нулевым с приведенным ниже утверждением

     latch.await();  
    
  5. Processor поток спит в течение 3 секунд, а затем он уменьшает значение счетчика с latch.countDown()
  6. первый Process экземпляр изменит количество защелок как 2 после его завершения из-за latch.countDown().

  7. второй Process экземпляр изменит защелку считаются 1 после его завершения из-за latch.countDown().

  8. третий Process экземпляр изменит количество защелок как 0 после его завершения из-за latch.countDown().

  9. нулевой счет на защелке вызывает основной поток App вышли из await

  10. программа приложения печатает этот вывод сейчас: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

    Ничего не найдено.