1. введение
В этом уроке мы сравним CyclicBarrier и CountDownLatch и попытаемся понять сходства и различия между ними.
2. Что Это Такое?
Когда дело доходит до параллелизма, может быть сложно концептуализировать то, что каждый из них предназначен для достижения.
Прежде всего, оба CountDownLatch и CyclicBarrier используются для управления многопоточными приложениями .
И, они оба предназначены для выражения того, как данный поток или группа потоков должны ждать.
2.1. Обратный отсчет
A CountDownLatch – это конструкция, в которой поток ждет , в то время как другие потоки обратный отсчет на защелке, пока он не достигнет нуля.
Мы можем думать об этом как о готовящемся блюде в ресторане. Независимо от того, какой повар готовит, сколько бы предметов n ни было, официант должен подождать , пока все предметы не окажутся на тарелке. Если тарелка принимает n предметов, любой повар будет отсчитывать на защелке для каждого предмета, который она кладет на тарелку.
2.2. Циклобарьер
/| CyclicBarrier – это многоразовая конструкция, в которой группа потоков ожидает вместе, пока все потоки не прибудут . В этот момент барьер сломан, и при необходимости может быть предпринято действие .
Мы можем думать об этом как о группе друзей. Каждый раз, когда они планируют поесть в ресторане, они решают общую точку, где они могут встретиться. Они ждут друг друга там, и только когда все прибудут они смогут пойти в ресторан, чтобы поесть вместе.
2.3. Дальнейшее Чтение
И для получения более подробной информации о каждом из них в отдельности обратитесь к нашим предыдущим учебным пособиям по CountDownLatch и CyclicBarrier соответственно.
3. Задачи против Нити
Давайте глубже погрузимся в некоторые семантические различия между этими двумя классами.
Как указано в определениях, CyclicBarrier позволяет нескольким потокам ждать друг друга, в то время как CountDownLatch позволяет одному или нескольким потокам ждать завершения ряда задач.
Короче говоря, CyclicBarrier поддерживает количество потоков , тогда как CountDownLatch поддерживает количество задач .
В следующем коде мы определяем CountDownLatch со счетом два. Затем мы вызываем Обратный отсчет() дважды из одного потока:
CountDownLatch countDownLatch = new CountDownLatch(2); Thread t = new Thread(() -> { countDownLatch.countDown(); countDownLatch.countDown(); }); t.start(); countDownLatch.await(); assertEquals(0, countDownLatch.getCount());
Как только защелка достигает нуля, возвращается вызов await .
Обратите внимание, что в этом случае мы смогли заставить один и тот же поток уменьшить количество в два раза.
Циклобарьер, хотя в этом вопросе все по-другому.
Аналогично приведенному выше примеру, мы создаем 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() фактически не будет вызван, пока барьер уже не будет преодолен!
В нашем тесте барьер не был преодолен, потому что у нас есть только один поток, ожидающий, а не два потока, которые потребуются для срабатывания барьера. Это также очевидно из cyclicBarrier.is Метод Broken () , который возвращает 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 .