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

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

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

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

}

Ответ 1

Да, вы правильно поняли. CountDownLatch работает по принципу защелки, основной поток будет ждать, пока ворота не будут открыты. Один поток ожидает n потоков, указанных при создании CountDownLatch.

Любой поток, обычно основной поток приложения, который вызывает CountDownLatch.await() будет ожидать, пока счетчик не достигнет нуля или не прервется другим потоком. Все остальные потоки должны вести CountDownLatch.countDown() отсчет, вызывая CountDownLatch.countDown() после их завершения или готовности.

Как только счет достигает нуля, ожидающий поток продолжается. Одним из недостатков/преимуществ CountDownLatch является то, что его нельзя использовать повторно: как только счетчик достигнет нуля, вы больше не сможете использовать CountDownLatch.

Редактировать:

Используйте CountDownLatch когда одному потоку (например, основному потоку) требуется дождаться завершения одного или нескольких потоков, прежде чем он сможет продолжить обработку.

Классическим примером использования CountDownLatch в Java является базовое Java-приложение на стороне сервера, которое использует архитектуру служб, где несколько служб предоставляются несколькими потоками, и приложение не может начать обработку, пока все службы не будут успешно запущены.

PS Вопрос OP имеет довольно простой пример, поэтому я его не включил.

Ответ 2

CountDownLatch в Java - это тип синхронизатора, который позволяет одному Thread ожидать один или несколько Thread, прежде чем он начнет обрабатывать.

CountDownLatch работает по принципу защелки, поток будет ждать, пока не откроется ворота. Один поток ожидает n количества потоков, указанных при создании CountDownLatch.

например. final CountDownLatch latch = new CountDownLatch(3);

Здесь мы устанавливаем счетчик на 3.

Любой поток, обычно основной поток приложения, который вызывает CountDownLatch.await(), будет ждать, пока счетчик не достигнет нуля или его прервет другой Thread. Все остальные потоки должны делать обратный отсчет, вызывая CountDownLatch.countDown() после их завершения или готовности к заданию. как только счетчик достигнет нуля, начнется запуск Thread.

Здесь счетчик получает декремент методом CountDownLatch.countDown().

Thread, который вызывает метод await(), будет ждать, пока начальный счет не достигнет нуля.

Чтобы сделать счетчик нулями, другие потоки должны вызвать метод countDown(). Когда счетчик станет нулевым, поток, который вызывается методом await(), возобновит (начнет его выполнение).

Недостатком CountDownLatch является то, что он не может использоваться повторно: после того, как счетчик станет нулевым, он больше не будет использоваться.

Ответ 3

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



     }
  }

Ответ 4

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

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

Задание завершено командой разработчиков devA

Задача, назначенная команде QA

Задание завершено командой QA

Здесь метод await() ожидает, что флаг countdownlatch станет 0, а метод countDown() уменьшает флаг countdownlatch на 1.

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

  • Когда мы используем ExecutorService вместо класса Thread для создания потоков.
  • Измените пример выше, когда Менеджер хочет передать код команде QA, как только Development завершит свою 80-процентную задачу. Это означает, что CountDownLatch позволяет нам изменять реализацию, которая может использоваться для ожидания другого потока для их частичного выполнения.

Ответ 5

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

Ответ 6

Хорошим примером того, когда использовать что-то подобное, является 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();
         }
     }
}

Ответ 7

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

latch.countDown();
System.out.println("DONE "+this.latch); // Add this debug

На выходе будет показано, что счетчик уменьшен. Этот "счет" представляет собой фактически количество запущенных задач (объектов процессора), с которыми вы начали, против которых вызывается функция countDown() not, и, следовательно, блокируется основной поток при его вызове на latch.await().

DONE [email protected][Count = 2]
DONE [email protected][Count = 1]
DONE java.ut[email protected][Count = 0]

Ответ 8

Из документации oracle о CountDownLatch:

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

A CountDownLatch инициализируется данным подсчетом. Методы await блокируют до тех пор, пока текущий счетчик не достигнет нуля из-за вызовов метода countDown(), после чего все ожидающие потоки освобождаются и любые последующие вызовы ожидания возвращаются немедленно. Это одноразовый феномен - счет не может быть reset.

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

A CountDownLatch, инициализированный подсчетом одного, служит в качестве простой вкл/выкл затвора или затвора: все потоки, вызывающие ожидание, ждут у ворот до тех пор, пока они не будут открыты потоком, вызывающим countDown().

A CountDownLatch, инициализированный до N, может использоваться для того, чтобы сделать один поток до тех пор, пока нити N не завершили какое-либо действие, или какое-либо действие было выполнено N раз.

public void await()
           throws InterruptedException

Заставляет текущий поток ждать, пока защелка не спустится до нуля, если поток не прерван.

Если текущий счетчик равен нулю, этот метод немедленно возвращается.

public void countDown()

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

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

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

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

    CountDownLatch latch = new CountDownLatch(3);
    
  • Вы передали этот общий latch поток Worker: Processor

  • Три Runnable экземпляра Processor были отправлены в ExecutorService executor
  • Основной поток (App) ожидает, что count станет нулевым с помощью оператора

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

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

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

  • Нулевой подсчет на защелке вызывает основной поток App для выхода из await

  • Программа приложения теперь печатает этот вывод: Completed

Ответ 9

Этот пример из Java Doc помог мне понять концепции:

class Driver { // ...
  void main() throws InterruptedException {
    CountDownLatch startSignal = new CountDownLatch(1);
    CountDownLatch doneSignal = new CountDownLatch(N);

    for (int i = 0; i < N; ++i) // create and start threads
      new Thread(new Worker(startSignal, doneSignal)).start();

    doSomethingElse();            // don't let run yet
    startSignal.countDown();      // let all threads proceed
    doSomethingElse();
    doneSignal.await();           // wait for all to finish
  }
}

class Worker implements Runnable {
  private final CountDownLatch startSignal;
  private final CountDownLatch doneSignal;
  Worker(CountDownLatch startSignal, CountDownLatch doneSignal) {
     this.startSignal = startSignal;
     this.doneSignal = doneSignal;
  }
  public void run() {
     try {
       startSignal.await();
       doWork();
       doneSignal.countDown();
     } catch (InterruptedException ex) {} // return;
  }

  void doWork() { ... }
}

Визуальная интерпретация:

enter image description here

Очевидно, что CountDownLatch позволяет одному потоку (здесь Driver) ждать, пока несколько запущенных потоков (здесь Worker) не закончат свое выполнение.

Ответ 10

Как упоминалось в 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.
"M" запросов: countDownLatch.await();
где как "T1", "T2", "T3" делает countDownLatch.countdown();

Недостатком метода соединения является то, что M должен знать о T1, T2, T3. Если позже добавлен новый рабочий поток T4, то M тоже должен об этом знать. Этого можно избежать с помощью CountDownLatch. После реализации последовательность действий будет [T1, T2, T3] (порядок T1, T2, T3 может быть в любом случае) → [M]

Ответ 11

Лучшее реальное время Пример для countDownLatch, описанный в этой ссылке CountDownLatchExample

Ответ 12

package practice;

import java.util.concurrent.CountDownLatch;

public class CountDownLatchExample {

    public static void main(String[] args) throws InterruptedException {
        CountDownLatch c= new CountDownLatch(3);  // need to decrements the count (3) to zero by calling countDown() method so that main thread will wake up after calling await() method 
        Task t = new Task(c);
        Task t1 = new Task(c);
        Task t2 = new Task(c);
        t.start();
        t1.start();
        t2.start();
        c.await(); // when count becomes zero main thread will wake up 
        System.out.println("This will print after count down latch count become zero");
    }
}

class Task extends Thread{
    CountDownLatch c;

    public Task(CountDownLatch c) {
        this.c = c;
    }

    @Override
    public void run() {
        try {
            System.out.println(Thread.currentThread().getName());
            Thread.sleep(1000);
            c.countDown();   // each thread decrement the count by one 
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}