Добро пожаловать в"Королевский класс параллелизма, эта статья является частью серииСтатья 21, в платинеГлава 8.
В предыдущей статье мы представили использование CountDownLatch. CountDownLatch — очень хороший выбор при координации начала и окончания многопоточности. CyclicBarrier, с которым вам познакомится в этой статье, еще более интересен, он имеет как сходство, так и очевидные различия с CountDownLatch с точки зрения возможностей, и его стоит посмотреть. Эта статья сначала поможет вам понять проблему с места происшествия, а затем понять решение, предлагаемое CyclicBarrier.
1. Первый опыт CyclicBarrier
1. Любовь в каньонном лесу
В реках и озерах каньона есть не только жизнь и смерть, мечи и мечи, но и прекрасные любовные истории.
Броня Бога Войны в каньоне когда-то спасла Да Цяо в критический момент, это героическое спасение красавицы заставило их искоренить искру любви, и они вдвоем встретились в каждом уголке каньона, если что-то случилось. в,каньон лесвот куда они ходили,Тот, кто придет первым, будет ждать другого, а когда они оба придут, сыграем вместе..
Здесь есть два важных момента. Во-первых, они хотятждем друг друга, а во-вторых, ведьиграть в. Теперь давайте представим, что бы вы сделали, если бы смоделировали этот сценарий в коде. Некоторые студенты могут сказать, что с ожиданием двух человек (потоков) справиться легко.Но что, если их три?
Таким образом, эту проблему сценария можно резюмировать следующим образом:Несколько потоков ждут друг друга, а затем выполняют определенные действия, когда все они завершены..
Далее мы будем использовать CyclicBarrier для моделирования и решения проблемы этой сцены и интуитивно почувствуем использование CyclicBarrier.
В следующем коде мы определяемНазначениеМесто,а такжеБольшой Джоа такжебронядва главных героя. После того, как все они достигают места встречи, мы подтверждаем это, выводя предложение, содержащее три розы 🌹🌹🌹, и посылаем им благословение.
private static String appointmentPlace = "峡谷森林";
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(2, () -> print("🌹🌹🌹到达约会地点:大乔和铠都来到了👉" + appointmentPlace));
Thread 大乔 = newThread("大乔", () -> {
say("铠,你在哪里...");
try {
cyclicBarrier.await(); //到达幽会地点
say("铠,你终于来了...");
} catch (Exception e) {
e.printStackTrace();
}
});
Thread 铠 = newThread("铠", () -> {
try {
Thread.sleep(500); //铠打野中
say("我打个野,马上就到!");
cyclicBarrier.await(); //到达幽会地点
say("乔,不好意思,刚打野遇上兰陵王了,你还好吗?!");
} catch (Exception e) {
e.printStackTrace();
}
});
大乔.start();
铠.start();
}
Результат выглядит следующим образом:
大乔:铠,你在哪里...
铠:我打个野,马上就到!
🌹🌹🌹到达约会地点:大乔和铠都来到了👉峡谷森林
铠:乔,不好意思,刚打野遇上兰陵王了,你还好吗?!
大乔:铠,你终于来了...
Process finished with exit code 0
Нет необходимости углубляться в детали кода, внутренние детали CyclicBarrier будут подробно объяснены далее в этой статье.Сначала почувствуйте его основное использование.
Как видно из результатов,CyclicBarrier может, подобно CountDownLatch, координировать действие завершения выполнения нескольких потоков и выполнять определенное действие после их завершения.. С этой точки зрения это сходство между CyclicBarrier и CountDownLatch. Однако следующая сцена показывает явную разницу между ними.
2. Свидание у реки
В приведенной выше сцене Кай уже упомянул, что встретил короля Ланьлина, играя в джунгли. Во время свидания Кая и Да Цяо король Ланьлин снова столкнулся с ними. Поэтому из-за беспокойства короля Ланьлина Кай и Да Цяо были вынуждены сдвинуть свои позиции.Они также договорились о новом месте встречи и стали ждать друг друга.. (Кай всегда думал, что королю Ланьлину тоже нравился Да Цяо, и он хотел завоевать любовь с ним.На самом деле, король Ланьлин заботился только о диких побоях Кая.В его сердце были только дикие монстры, и он не интересовался ни одной женщиной.).
На этом этапе, если вы продолжите моделировать эту сцену с помощью кода, то CountDownLatch бессилен.Поскольку использование CountDownLatch является одноразовым, нельзя использовать повторно. В настоящее время,Вы найдете волшебство CyclicBarrier, его можно использовать повторно. Кажется, вы уже поняли, почему он называетсяCyclicпричина.
Теперь давайте возьмем еще один фрагмент кода для имитации второго свидания между Да Цяо и Каем. В коде мы по-прежнему определяем место встречи, Да Цяо и Кай, два главных героя.Но в отличие от предыдущего, мы также добавили короля Ланьлина в качестве спойлера и изменили место встречи на полпути..
private static String appointmentPlace = "峡谷森林";
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(2, () -> System.out.println("🌹🌹🌹到达约会地点:大乔和铠都来到了👉" + appointmentPlace));
Thread 大乔 = newThread("大乔", () -> {
say("铠,你在哪里...");
try {
cyclicBarrier.await();
say("铠,你终于来了...");
Thread.sleep(2600); //约会中...
say("好的,你要小心!");
cyclicBarrier.await(); // 注意这里是第二次调用await
Thread.sleep(100);
say("我愿意!");
} catch (Exception e) {
e.printStackTrace();
}
});
Thread 铠 = newThread("铠", () -> {
try {
Thread.sleep(500); //铠打野中
say("我打个野,马上就到!");
cyclicBarrier.await(); //到达幽会地点
say("乔,不好意思,刚打野遇上兰陵王了,你还好吗?!");
Thread.sleep(1500); //幽会中...
note("幽会中...\n");
Thread.sleep(1000); //幽会中...
say("这个该死的兰陵王!乔,你先走,小河边见!"); //铠突然看到了兰陵王
appointmentPlace = "小河边"; // 铠把地点改成了小河边
Thread.sleep(1500); //和兰陵王对决中...
note("︎\uD83D\uDDE1\uD83D\uDD2A铠和兰陵王决战开始,最终铠杀死了兰陵王,并前往小河边...\n");
cyclicBarrier.await(); // 杀了兰陵王后,铠到了小河边 !!!注意这里是第二次调用await
say("乔,我已经解决了兰陵王,你看今晚夜色多美,我陪你看星星到天明...");
} catch (Exception ignored) {}
});
Thread 兰陵王 = newThread("兰陵王", () -> {
try {
Thread.sleep(2500);
note("兰陵王出场...");
say("铠打了我的野,不杀他誓不罢休!");
say("铠,原来你和大乔在这里!\uD83D\uDDE1️\uD83D\uDDE1️");
} catch (Exception ignored) {}
});
兰陵王.start();
大乔.start();
铠.start();
}
Результат показан ниже.После того, как король Ланьлин разрушил хорошие дела в Кайонском лесу, Кай был так зол, что позволил Да Цяо идти первым и согласился встретиться у небольшой реки. После этого Кай обезглавил короля Ланьлина (бедного прямолинейного человека из стали) и отправился в Сяохэ, чтобы завершить свое второе свидание с Да Цяо..
大乔:铠,你在哪里...
铠:我打个野,马上就到!
🌹🌹🌹到达约会地点:大乔和铠都来到了👉峡谷森林
铠:乔,不好意思,刚打野遇上兰陵王了,你还好吗?!
大乔:铠,你终于来了...
幽会中...
兰陵王出场...
兰陵王:铠打了我的野,不杀他誓不罢休!
兰陵王:铠,原来你和大乔在这里!🗡️🗡️
铠:这个该死的兰陵王!乔,你先走,小河边见!
大乔:好的,你要小心!
︎🗡🔪铠和兰陵王决战开始,最终铠杀死了兰陵王,并前往小河边...
🌹🌹🌹到达约会地点:大乔和铠都来到了👉小河边
铠:乔,我已经解决了兰陵王,你看今晚夜色多美,我陪你看星星到天明...
大乔:真好!
Process finished with exit code 0
Точно так же вы пока игнорируете детали кода,Но вы должны заметить, что Кай и Да Цяо противостоят друг другу.await()
два звонка. Вы можете быть удивлены тем, насколько это удивительно, пока не поймете, как это работает, что нормально.
2. Как реализован CyclicBarrier?
CyclicBarrier — это средство синхронизации потоков, предоставляемое в Java, похожее на CountDownLatch, но не совсем такое же.Основное отличие состоит в том, что CyclicBarrier можно перерабатывать, что также отражено в его названии..
Далее давайте проанализируем его конкретную реализацию исходного кода.
1. Основные структуры данных
-
private final ReentrantLock lock = new ReentrantLock()
: Есть только один замок для входа в шлагбаум; -
private final Condition trip = lock.newCondition()
: используется вместе с вышеуказанным замком; -
private final int parties
: Количество участников, приведенные выше примеры в этой статье только Кай и Да Цяо, поэтому число равно 2; -
private final Runnable barrierCommand
: специальный код для запуска в конце этого раунда. Он используется в приведенных выше примерах этой статьи, и вы можете просмотреть его; -
private Generation generation = new Generation()
: Генерация текущего барьера. Например, в двух сценариях, упомянутых выше в этой статье, генерация различна: после того, как Кай и Да Цяо поменяют место встречи на берег реки, будет сгенерирована новая генерация; -
private int count
: количество ожидающих сторон. В каждом поколении count уменьшается от начального количества участников (т.е. сторон) до 0, при 0 генерация заканчивается, и когда новое поколение или это поколение прерывается (ломается), значение count возвращается к значению партий .
2. Основная структура
-
public CyclicBarrier(int parties)
: указывает количество участников; -
public CyclicBarrier(int parties, Runnable barrierAction)
: Укажите количество сторон и укажите код для запуска в конце этого поколения.
3. Основные методы
-
public int await()
: если текущий поток не достиг барьера первым, он войдет в ожидание, пока все остальные потоки не достигнут его, если только это не произойдет.прерванный,барьер снят,Барьер сброшентак далее.; -
public int await(long timeout, TimeUnit unit)
: Аналогично await(), но с ограничением по времени; -
public boolean isBroken()
: Был ли удален текущий барьер; -
public void reset()
: Сбросить текущий барьер. Сначала будет снят барьер, а затем будет установлен новый барьер; -
public int getNumberWaiting()
: количество ожидающих потоков.
Среди различных методов CyclicBarrier основным являетсяdowait()
,дваawait()
Этот метод вызывается внутренне. Итак, поймитеdowait()
, в основном поймите ключ к реализации CyclicBarrier.
dowait()
Этот метод немного длиннее и требует немного терпения, и я прокомментировал некоторые из них. Конечно, если вы хотите увидеть исходный код, рекомендуется смотреть все это прямо из JDK, исходный код здесь просто для того, чтобы помочь вам понять контекст.
private int dowait(boolean timed, long nanos)
throws InterruptedException, BrokenBarrierException,TimeoutException {
final ReentrantLock lock = this.lock;
lock.lock(); // 注意,这里是一定要加锁的
try {
final Generation g = generation;
if (g.broken) // 如果当前屏障被拆除,则抛出异常
throw new BrokenBarrierException();
if (Thread.interrupted()) {
breakBarrier(); // 如果当前线程被中断,则拆除屏障并抛出异常
throw new InterruptedException();
}
int index = --count; // 当线程调用await后,count减1
if (index == 0) { // tripped // 如果count为0,接下来将尝试结束屏障,并开启新的屏障
boolean ranAction = false;
try {
final Runnable command = barrierCommand;
if (command != null)
command.run();
ranAction = true;
nextGeneration();
return 0;
} finally {
if (!ranAction)
breakBarrier();
}
}
// loop until tripped, broken, interrupted, or timed out
for (;;) {
try {
if (!timed)
trip.await();
else if (nanos > 0 L)
nanos = trip.awaitNanos(nanos);
} catch (InterruptedException ie) {
if (g == generation && !g.broken) {
breakBarrier();
throw ie;
} else {
// We're about to finish waiting even if we had not
// been interrupted, so this interrupt is deemed to
// "belong" to subsequent execution.
Thread.currentThread().interrupt();
}
}
if (g.broken)
throw new BrokenBarrierException();
if (g != generation)
return index;
if (timed && nanos <= 0 L) {
breakBarrier();
throw new TimeoutException();
}
}
} finally {
lock.unlock();
}
}
Для основных структур данных, конструкций и методов CyclicBarrier все вышеперечисленное важно. Но, что более важно, чтобы понять идею CyclicBarrier,Вот эта картинка достойна вашей коллекции.Поймите эту картину, также поймите CyclicBarrier.
В это время, оглядываясь назад на две сцены в первом разделе этой картины, Кай и Да Цяо находятся в состоянииканьон лес,берег рекиВстреча в двух местах. Тогда, если это еще и представлено картинкой, то должно быть так:
3. В чем разница между CyclicBarrier и CountDownLatch
Основные различия между ними были упомянуты в предыдущих двух разделах:
- CountDownLatch одноразовый, а CyclicBarrier может устанавливать барьеры несколько раз для повторного использования.;
- Каждый дочерний поток в CountDownLatch не может ждать других потоков и может выполнять только свои собственные задачи, в то время как каждый поток в CyclicBarrier может ждать других потоков..
Кроме того, между ними есть некоторые другие различия, как показано в следующей таблице после общего резюме:
CyclicBarrier | CountDownLatch |
---|---|
CyclicBarrier можно использовать повторно, когда потоки ждут, пока все потоки завершат свои задачи. В это время шлагбаум будет демонтирован и может дополнительно выполнять определенные действия. | CountDownLatch является одноразовым, разные потоки работают с одним и тем же счетчиком, пока счетчик не достигнет 0. |
CyclicBarrier для количества потоков | CountDownLatch для количества задач |
При использовании CyclicBarrier необходимо указать количество потоков, участвующих в кооперации при построении, и эти потоки должны вызывать метод await() | При использовании CountDownLatch необходимо указывать количество задач, и не имеет значения, какие потоки завершают эти задачи |
CyclicBarrier можно использовать повторно после освобождения всех потоков. | CountDownLatch больше нельзя использовать, когда счетчик равен 0. |
В CyclicBarrier, если поток сталкивается с такими проблемами, как прерывание, тайм-аут и т. д., у потока, находящегося в ожидании, возникнут проблемы. | В CountDownLatch, если есть проблема с одним потоком, другие потоки не затрагиваются. |
резюме
Это все, что касается CyclicBarrier. Изучая CyclicBarrier, мы должны сосредоточиться на понимании сценариев проблем, которые он хочет решить, и чем он отличается от CountDownLatch, а затем посмотреть на исходный код. Конечно, если эта история с собачьей кровью может заставить вас вспомнить этот пункт знаний, собачья кровь тоже того стоит.
На этом текст окончен, поздравляю с получением очередной звезды✨
Испытание мастера
- Напишите код, чтобы испытать использование CyclicBarrier.
Дальнейшее чтение и ссылки
Об авторе
Почти десять лет работал в agile и DevOps консалтинге, техническим руководителем и менеджментом, имеет богатый практический опыт в распределенной архитектуре с высоким параллелизмом. Увлеченный обменом технологиями и переводом книг в конкретных областях, буклет Nuggets "Суть дизайна и реализация всплеска высокой параллелизма"автор.
Подпишитесь на официальный аккаунт [MetaThoughts], чтобы своевременно получать обновления статей и рукописи.
Если эта статья была вам полезна, добро пожаловатьподобно,обрати внимание на,контролировать,Вместе мы будемОт бронзы до короля.