Java CyclicBarrier против CountDownLatch

Java CyclicBarrier против CountDownLatch

1. Вступление

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

2. Что это?

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

В первую очередь, какCountDownLatch and CyclicBarrier are used for managing multi-threaded applications.

И,they are both intended to express how a given thread or group of threads should wait.

2.1. CountDownLatchс

CountDownLatch - это конструкция, на которой потокwaits, в то время как другие потокиcount down на защелке, пока он не достигнет нуля.

Мы можем думать об этом как о блюде в ресторане, который готовится. Независимо от того, какой повар готовит, сколько бы ни былоitems, официант долженwait, пока все продукты не будут на тарелке. Если тарелка занимаетitems, любой повар будетcount down установить защелку для каждого предмета, который она кладет на тарелку.

2.2. CyclicBarrierс

CyclicBarrier  - это многократно используемая конструкция, в которой группа потоковwaits вместе до всех потоковarrive. В этот момент барьер нарушен, и можно дополнительно взятьaction.

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

2.3. Дальнейшее чтение

И для получения более подробной информации о каждом из них по отдельности обратитесь к нашим предыдущим руководствам поCountDownLatch иCyclicBarrier соответственно.

3. Задачи против Потоки

Давайте глубже погрузимся в некоторые семантические различия между этими двумя классами.

Как указано в определениях,CyclicBarrier позволяет нескольким потокам ждать друг друга, тогда какCountDownLatch позволяет одному или нескольким потокам ждать завершения ряда задач.

Короче говоря,CyclicBarrier maintains a count of threads, тогда какCountDownLatch maintains a count of tasks.

В следующем коде мы определяемCountDownLatch со счетом два. Затем мы дважды вызываемcountDown() из одного потока:

CountDownLatch countDownLatch = new CountDownLatch(2);
Thread t = new Thread(() -> {
    countDownLatch.countDown();
    countDownLatch.countDown();
});
t.start();
countDownLatch.await();

assertEquals(0, countDownLatch.getCount());

Как только защелка достигает нуля, вызовawait  возвращается.

Отметим, что в этом случаеwe were able to have the same thread decrease the count twice.

CyclicBarrier, в этом вопросе отличается.

Подобно приведенному выше примеру, мы снова создаемCyclicBarrier, со счетом два и вызываем для негоawait(), на этот раз из того же потока:

CyclicBarrier cyclicBarrier = new CyclicBarrier(2);
Thread t = new Thread(() -> {
    try {
        cyclicBarrier.await();
        cyclicBarrier.await();
    } catch (InterruptedException | BrokenBarrierException e) {
        // error handling
    }
});
t.start();

assertEquals(1, cyclicBarrier.getNumberWaiting());
assertFalse(cyclicBarrier.isBroken());

Первое отличие здесь в том, что ожидающие потоки сами являются барьером.

Во-вторых, что более важно,the second await() is useless. A single thread can’t count down a barrier twice.

Действительно, посколькуt долженwait, чтобы другой поток вызвалawait() - чтобы довести счет до двух, - второй вызовt кawait() на самом деле не быть вызванным, пока барьер уже не будет сломан!

В нашем тестеthe barrier hasn’t been crossed because we only have one thread waiting and not the two threads that would be required for the barrier to be tripped.. Это также видно из методаcyclicBarrier.isBroken(), который возвращаетfalse.

4. Повторное использование

Второе наиболее очевидное различие между этими двумя классами - возможность повторного использования. Чтобы уточнить,when the barrier trips in CyclicBarrier, the count resets to its original value.CountDownLatch is different because the count never resets.

В данном коде мы определяемCountDownLatch со счетом 7 и считаем его через 20 различных вызовов:

CountDownLatch countDownLatch = new CountDownLatch(7);
ExecutorService es = Executors.newFixedThreadPool(20);
for (int i = 0; i < 20; i++) {
    es.execute(() -> {
        long prevValue = countDownLatch.getCount();
        countDownLatch.countDown();
        if (countDownLatch.getCount() != prevValue) {
            outputScraper.add("Count Updated");
        }
    });
}
es.shutdown();

assertTrue(outputScraper.size() <= 7);

Мы наблюдаем, что хотя 20 разных потоков вызываютcountDown(), счетчик не сбрасывается, когда достигает нуля.

Подобно приведенному выше примеру, мы определяемCyclicBarrier with count 7 и ждем его от 20 различных потоков:

CyclicBarrier cyclicBarrier = new CyclicBarrier(7);

ExecutorService es = Executors.newFixedThreadPool(20);
for (int i = 0; i < 20; i++) {
    es.execute(() -> {
        try {
            if (cyclicBarrier.getNumberWaiting() <= 0) {
                outputScraper.add("Count Updated");
            }
            cyclicBarrier.await();
        } catch (InterruptedException | BrokenBarrierException e) {
            // error handling
        }
    });
}
es.shutdown();

assertTrue(outputScraper.size() > 7);

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

5. Заключение

В общем,CyclicBarrier иCountDownLatch  - полезные инструменты для синхронизации между несколькими потоками. Тем не менее, они принципиально отличаются с точки зрения функциональности, которую они предоставляют. Тщательно обдумайте каждый вопрос, чтобы определить, какой из них подходит для работы.

Как обычно, все рассмотренные примеры доступныover on Github.