Как используется CountDownLatch в многопоточности Java?

может кто-нибудь помочь мне понять, что Java CountDownLatch и когда его использовать?

у меня нет очень четкого представления о том, как работает эта программа. Как я понимаю, все три потока начинаются сразу, и каждый поток будет вызывать CountDownLatch после 3000ms. Так что отсчет будет уменьшаться один за другим. После того, как защелка становится нулевой, программа печатает "завершено". Возможно, то, как я понял, неверно.

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() метод.

на 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....");



     }
  }

Он используется, когда мы хотим дождаться более одного потока для выполнения своей задачи. Это похоже на join in threads.

где мы можем использовать 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

здесь await () метод ожидает флаг countdownlatch, чтобы стать 0, и обратный отсчет() метод уменьшает флаг countdownlatch на 1.

ограничение соединения: Приведенный выше пример также может быть достигнут с помощью JOIN, но JOIN нельзя использовать в двух сценариях:

  1. когда мы используем ExecutorService вместо потока класс для создания потоков.
  2. изменить пример выше, где менеджер хочет передать код команде QA, как только разработка завершит свою задачу 80%. Это означает, что CountDownLatch позволяет нам изменять реализацию, которая может использоваться для ожидания другого потока для их частичного выполнения.

один хороший пример того, когда использовать что-то вроде этого-с Java Simple Serial Connector, доступ к последовательным портам. Обычно вы пишете что-то в порт, и asyncronously, в другом потоке, устройство будет отвечать на 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

Если вы добавите некоторую отладку после вызова защелки.countDown (), это может помочь вам лучше понять его поведение.

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 инициализируется с заданным количеством. The await блок методов до тех пор, пока текущий счетчик не достигнет нуля из-за вызовов countDown() метод, после которого освобождаются все ожидающие потоки и любые последующие вызовы жду возвращения немедленно. Это однократное явление-счет не может быть сброшен.

CountDownLatch является универсальным инструментом синхронизации и может использоваться для ряда целей.

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 были представлены ExecutorService executor
  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