Як CountDownLatch використовується в багатопотоковій програмі Java?


183

Чи може хтось допомогти мені зрозуміти, що таке Java CountDownLatchі коли її використовувати?

Я не дуже чітко уявляю, як працює ця програма. Як я розумію, всі три теми починаються одразу, і кожна нитка зателефонує CountDownLatch через 3000 мс. Тож підрахунок буде зменшенням один за одним. Після того, як засувка стане нульовою, програма надрукує "Завершено". Можливо, те, як я зрозумів, є невірним.

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

}


8
Я щойно використав зразок коду вашого питання для пакету сервісів паралельної служби Android, і він спрацював як принадність. Дуже дякую!
Roisgoen

Тут я взяв це відео з 2012 року, яке демонструє неабияку схожість із наведеним тут прикладом. Для всіх, хто цікавиться, це частина навчальної серії з багатопотоковою навчальною програмою Java від хлопця на ім'я Джон. Мені подобається Джон. Настійно рекомендується.
Elia Grady

Відповіді:


194

Так, ви правильно зрозуміли. CountDownLatchпрацює за принципом засувки, головна нитка буде чекати, поки ворота будуть відкриті. Один потік чекає n ниток, вказаних під час створення CountDownLatch.

Будь-який потік, як правило, основний потік програми, який викликає, CountDownLatch.await()буде чекати, поки кількість досягне нуля або його перерве інший потік. Усі інші потоки потрібно відлічити, зателефонувавши, CountDownLatch.countDown()коли вони завершені чи готові.

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

Редагувати:

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

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

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


1
Дякую за відповідь Чи можете ви надати мені приклад, де застосувати засувку CountDown?
amal

11
підручник про те, як користуватися CountDownLatch - ось як howtodoinjava.com/2013/07/18/…
thiagoh

1
@NikolaB Але в цьому наведеному прикладі ми можемо досягти такого ж результату, використовуючи метод приєднання, чи не так?
Vikas Verma

3
Я вважав би невикористаність перевагою: ви впевнені, що ніхто не може її скинути або збільшити кількість.
ataulm

3
Приємне пояснення. Але я б трохи не погодився з цим питанням One thread waits for n number of threads specified while creating CountDownLatch in Java. Якщо вам потрібен такий механізм, то його доцільно використовувати CyclicBarrier. Фундаментальне концептуальне розходження між цими двома, як зазначено в Java concurrency in Practiceце: Latches are for waiting for events; barriers are for waiting for other threads. cyclicBarrier.await()переходить у стан блокування.
Рахул Дев Мішра

43

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є те, що він не може бути використаний повторно: коли кількість стає нульовою, вона більше не може бути використана.


Чи використовуємо ми, new CountDownLatch(3)як у нас є 3 потоки з newFixedThreadPool визначеного?
Chaklader Asfak Arefe

не повинен "до того, як він почне обробку" бути "перед тим, як продовжити обробку"?
Марія Інес Парнісарі

@Arefe Так, це кількість потоків, що проходять через ваш блок коду
Vishal Akkalkote

23

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



     }
  }

22

Він використовується тоді, коли ми хочемо почекати більше ніж один потік, щоб виконати своє завдання. Це схоже на об’єднання в нитки.

Де ми можемо використовувати 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

Завдання виконано командою з контролю якості

Тут очікує () метод очікує, коли прапор countdownlatch стане 0, і countDown () зменшить прапор countdownlatch на 1.

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

  1. Коли ми використовуємо ExecutorService замість класу Thread для створення ниток.
  2. Змініть вище приклад, коли менеджер хоче передати код команді з контролю якості, як тільки розробник виконає їх 80% завдання. Це означає, що CountDownLatch дозволяють нам змінювати реалізацію, яка може використовуватися для очікування чергового потоку для їх часткового виконання.

3

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

Можливо, ви захочете перенести весь свій опис із блоку коду
Пол Ло,

Найкращий коментар, хоча. Мені подобаються ці "до суті" коментарі замість теоретичних пояснень.
renatoaraujoc

2

Хороший приклад, коли потрібно використовувати щось подібне, - це простий послідовний з'єднувач Java, який має доступ до послідовних портів. Зазвичай ви щось запишете на порт, і асинхронно, в іншому потоці, пристрій відповість на 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();
         }
     }
}


2

Якщо ви додасте якусь налагодження після свого дзвінка до latch.countDown (), це може допомогти вам краще зрозуміти його поведінку.

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

На виході буде показано зменшення кількості підрахунків. Це "підрахунок" - це фактично кількість завдань, що виконуються (Об'єкти процесора), які ви розпочали, проти яких countDown () не викликався, і тому блокується головний потік при його виклику до 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]

2

З документації на oracle про CountDownLatch :

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

A CountDownLatchініціалізується із заданим числом. Ці awaitметоди блокують доти , поточний значення лічильника досягає нуля не пов'язане з викликами countDown()методу, після чого всі потоки , які очікують будуть звільнені і будь-яких подальших викликах чекати повернення негайно. Це явище однократне - підрахунок не може бути скинутий.

CountDownLatch - це універсальний інструмент синхронізації і може використовуватися для багатьох цілей.

CountDownLatchІніціалізується з лічильником одного служить простий вкл / викл, засувка або воріт: для всіх потоків , що посилаються чекають чекати в воротах , поки не буде відкритий з допомогою різьблення виклику CountDown ().

CountDownLatchІніціалізується N може бути використаний , щоб зробити одну нитку чекати , поки N потоків не виконали яке - яку дію, або яке - то дія була завершено N раз.

public void await()
           throws InterruptedException

Примушує поточний потік чекати, поки засувка не підрахується до нуля, якщо тільки потік не буде перерваний.

Якщо поточна кількість дорівнює нулю, цей метод повертається негайно.

public void countDown()

Зменшує кількість засувки, звільняючи всі очікувані нитки, якщо кількість досягає нуля.

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

Пояснення вашого прикладу.

  1. Ви встановили кількість для latchзмінної 3

    CountDownLatch latch = new CountDownLatch(3);
  2. Ви передали цей спільний доступ latchдо потоку Worker: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


2

Цей приклад з 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() { ... }
}

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

введіть тут опис зображення

Очевидно, CountDownLatchдозволяє одному потоку (тут Driver) зачекати, поки буде виконано купу запущених ниток (тут Worker) з їх виконанням.


1

Як згадується в 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();

Одним із недоліків методу з'єднання є те, що М повинен знати про T1, T2, T3. Якщо є нова робоча нитка T4, додана пізніше, то M має знати про це також. Цього можна уникнути за допомогою CountDownLatch. Після реалізації послідовність дій буде [T1, T2, T3] (порядок T1, T2, T3 може бути будь-яким) -> [M]



0
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();
        }
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.