Перейти к основному содержимому

Java CyclicBarrier против CountDownLatch

· 4 мин. чтения

1. Введение

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

2. Что это?

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

Прежде всего, и CountDownLatch, и CyclicBarrier используются для управления многопоточными приложениями .

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

2.1. Защелка

CountDownLatch — это конструкция, которую поток ожидает , в то время как другие потоки отсчитывают защелку до тех пор, пока она не достигнет нуля . ``

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

2.2. циклическийбарьер

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

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

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

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

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

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

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

Короче говоря, CyclicBarrier поддерживает количество потоков , тогда как CountDownLatch поддерживает количество задач .

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

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

assertEquals(0, countDownLatch.getCount());

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

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

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());

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

Во-вторых, и что более важно, второй метод await() бесполезен . Один поток не может отсчитывать барьер дважды.

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

В нашем тесте барьер не был преодолен, потому что у нас есть только один ожидающий поток, а не два потока, которые потребуются для отключения барьера. Это также видно из метода cycloBarrier.isBroken() , который возвращает false .

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

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

В данном коде мы определяем 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 со счетчиком 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 являются полезными инструментами для синхронизации между несколькими потоками. Тем не менее, они принципиально отличаются с точки зрения функциональности, которую они обеспечивают. Внимательно рассмотрите каждый из них, чтобы определить, какой из них подходит для данной работы.

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