Java — сведения об использовании CountDownLatch

Java

Описание CountDownLatch

Обзор защелки обратного отсчета

  1. CountDownLatchОбычно используется как счетчик обратного отсчета для многопоточности, заставляя их ждать другую группу (CountDownLatchРешение об инициализации) выполнение задачи завершено.
  2. Следует отметить, чтоCountDownLatchПосле инициализации, когда значение счетчика уменьшается до 0, его нельзя восстановить.Semaphore,Semaphoreда черезreleaseОперация восстанавливает семафор.

Как работает CountDownLatch

Принцип использования

  1. Создайте CountDownLatch и установите значение счетчика.
  2. Запустите многопоточность и вызовите метод countDown() экземпляра CountDownLatch.
  3. вызов основного потокаawait()метод, так что работа основного потока будет блокироваться в этом методе до тех пор, пока другие потоки не завершат свои соответствующие задачи, значение счетчика равно 0, прекратит блокировку, а основной поток продолжит выполнение.

Используйте шаблоны

public class CountDownLatchModule {

    //线程数
    private static int N = 10;

    // 单位:min
    private static int countDownLatchTimeout = 5;

    public static void main(String[] args) {
        //创建CountDownLatch并设置计数值,该count值可以根据线程数的需要设置
        CountDownLatch countDownLatch = new CountDownLatch(N);

		//创建线程池
        ExecutorService cachedThreadPool = Executors.newCachedThreadPool();

        for (int i = 0; i < N; i++) {
            cachedThreadPool.execute(() ->{
                try {
                    System.out.println(Thread.currentThread().getName() + " do something!");
                } catch (Exception e) {
                    System.out.println("Exception: do something exception");
                } finally {
                    //该线程执行完毕-1
                    countDownLatch.countDown();
                }
            });
        }

        System.out.println("main thread do something-1");
        try {
            countDownLatch.await(countDownLatchTimeout, TimeUnit.MINUTES);
        } catch (InterruptedException e) {
            System.out.println("Exception: await interrupted exception");
        } finally {
            System.out.println("countDownLatch: " + countDownLatch.toString());
        }
        System.out.println("main thread do something-2");
        //若需要停止线程池可关闭;
//        cachedThreadPool.shutdown();

    }

результат операции:

main thread do something-1
pool-1-thread-1 do something!
pool-1-thread-2 do something!
pool-1-thread-3 do something!
pool-1-thread-5 do something!
pool-1-thread-6 do something!
pool-1-thread-7 do something!
pool-1-thread-8 do something!
pool-1-thread-4 do something!
pool-1-thread-9 do something!
pool-1-thread-10 do something!
countDownLatch: java.util.concurrent.CountDownLatch@76fb509a[Count = 0]
main thread do something-2

Общие методы CountDownLatch

在这里插入图片描述

  • public void await() throws InterruptedException:перечислитьawait()Поток метода будет приостановлен, ожидая, покаcountзначение0Продолжайте выполнять снова.
  • public boolean await(long timeout, TimeUnit unit) throws InterruptedException:такой жеawait()Если ожиданиеtimeoutПосле долгого времени,countЗначение по-прежнему не изменилось на 0, больше не ждите, продолжайте выполнять. В качестве единиц времени обычно используются миллисекунды, дни, часы, микросекунды, минуты, наносекунды, секунды.
    在这里插入图片描述
  • public void countDown(): значение счетчика уменьшается на 1.
  • public long getCount(): Получите текущую стоимость подсчета.
  • public String toString(): переопределить метод toString() и вывести больше значений счетчика. Подробности см. в исходном коде.

Сценарии использования CountDownLatch

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

Исходный код CountDownLatch

Исходный код конструктора

    /**
     * Constructs a {@code CountDownLatch} initialized with the given count.
     *
     * @param count the number of times {@link #countDown} must be invoked
     *        before threads can pass through {@link #await}
     * @throws IllegalArgumentException if {@code count} is negative
     */
    public CountDownLatch(int count) {
        if (count < 0) throw new IllegalArgumentException("count < 0");
        this.sync = new Sync(count);
    }

Исходный код метода toString()

    /**
     * Returns a string identifying this latch, as well as its state.
     * The state, in brackets, includes the String {@code "Count ="}
     * followed by the current count.
     *
     * @return a string identifying this latch, as well as its state
     */
    public String toString() {
        return super.toString() + "[Count = " + sync.getCount() + "]";
    }

Пример обратного отсчета

как сигнал запуска потока

код

public class CountDownLatchTest {

    /**
     * a start signal that prevents any worker from proceeding
     * until the driver is ready for them to proceed;
     * @param args
     * @throws InterruptedException
     */
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch startSignal = new CountDownLatch(1);
        CountDownLatch doneSignal = new CountDownLatch(10);
        for (int i = 0; i < 10; i++) {
            // create and start threads
            new Thread(new Worker(startSignal, doneSignal)).start();
        }
        // don't let run yet
        System.out.println("do something else 1");
        // let all threads proceed
        startSignal.countDown();
        System.out.println("do something else 2");
        // wait for all to finish
        doneSignal.await();
        System.out.println("wait for all to finsh");
    }

    static class Worker implements Runnable{

        private final CountDownLatch startSignal;
        private final CountDownLatch doneSignal;

        Worker(CountDownLatch startSignal, CountDownLatch doneSignal) {
            this.startSignal = startSignal;
            this.doneSignal = doneSignal;
        }

        @Override
        public void run() {
            try {
                startSignal.await();
                doWork();
                doneSignal.countDown();
            } catch (InterruptedException ex) {
                ex.printStackTrace();
            }
        }

        void doWork() {
            System.out.println("do work!");
        }
    }

}

результат операции

do something else 1
do something else 2
do work!
do work!
do work!
do work!
do work!
do work!
do work!
do work!
do work!
do work!
wait for all to finsh

Из бегущих результатов видно, что:

  1. Основной поток печатается первымdo something else 1иdo something else 2. так какstartSignal.countDown();После того, как счетчик станет равным 0, дочерний поток может печатать.
  2. так какstartSignal.await();находится внутри дочернего потока, все дочерние потоки ожидаютstartSignal.countDown()После выполнения для печатиdo work!.
  3. doneSignal.await();После ожидания выполнения всех дочерних потоков каждый разdoneSignal.countDown(), последний счетчик равен 0, основной поток выполняет печатьwait for all to finsh.

Пока поток ожидает сигнала завершения

код

public class CountDownLatchTest2 {

    /**
     * a completion signal that allows the driver to wait
     * until all workers have completed.
     * @param args
     * @throws InterruptedException
     */
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch doneSignal = new CountDownLatch(5);
        ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
        for (int i = 0; i < 10; i++) {
            // create and start threads
            cachedThreadPool.execute(new Worker(doneSignal, i));
        }
        // don't let run yet
        System.out.println("do something else 1");
        // wait for all to finish
        doneSignal.await();
        System.out.println("===========================count: " + doneSignal.getCount());
        System.out.println("do something else 2");
        cachedThreadPool.shutdown();
    }

    static class Worker implements Runnable{

        private final CountDownLatch doneSignal;
        private final int i;

        Worker(CountDownLatch doneSignal, int i) {
            this.doneSignal = doneSignal;
            this.i = i;
        }

        @Override
        public void run() {
            try {
                doWork();
                doneSignal.countDown();
                System.out.println("i = " + i + ", " + doneSignal.toString());
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }

        void doWork() {
            System.out.println("do work!");
        }
    }

}

результат операции

do something else 1
do work!
i = 0, java.util.concurrent.CountDownLatch@128abd43[Count = 4]
do work!
i = 1, java.util.concurrent.CountDownLatch@128abd43[Count = 3]
do work!
i = 2, java.util.concurrent.CountDownLatch@128abd43[Count = 2]
do work!
i = 3, java.util.concurrent.CountDownLatch@128abd43[Count = 1]
do work!
i = 4, java.util.concurrent.CountDownLatch@128abd43[Count = 0]
===========================count: 0
do something else 2
do work!
i = 5, java.util.concurrent.CountDownLatch@128abd43[Count = 0]
do work!
i = 6, java.util.concurrent.CountDownLatch@128abd43[Count = 0]
do work!
i = 7, java.util.concurrent.CountDownLatch@128abd43[Count = 0]
do work!
i = 8, java.util.concurrent.CountDownLatch@128abd43[Count = 0]
do work!
i = 9, java.util.concurrent.CountDownLatch@128abd43[Count = 0]

Из результатов выполнения видно, что основной поток ожидает выполнения других потоков 5 раз перед печатью.do something else 2информацию, поскольку значение CountDownLatch равно 5.