предисловие
В предыдущей статье, посвященной CountDownLatch, CountDown может обеспечить многопоточную координацию, и основной поток выполняет задачу после завершения всех указанных потоков.
Однако у CountDownLatch есть недостаток, о котором также говорится в документации JDK: его можно использовать только один раз. В некоторых случаях это кажется немного расточительным, и необходимо постоянно создавать экземпляры CountDownLatch.JDK познакомил нас с CyclicBarrier в документации CountDownLatch — круговой забор. Для конкретного использования см. статьюКласс инструмента совместной работы потоков для параллельного программирования.
Анализ исходного кода
Структура класса следующая:
Существует метод, который мы обычно используем, await, и внутренний класс Generation с одним параметром. Что он делает?
В CyclicBarrier есть понятие «генерация», потому что CyclicBarrier можно использовать повторно, поэтому каждый раз, когда все потоки проходят забор, это означает, что прошла генерация, как и наш новый год. Когда все пересекают Новый год, календарь обновляется.
Зачем это нужно? Позже, когда мы посмотрим на исходный код, мы поговорим об этом подробно, но сейчас это непросто понять.
Снова взглянув на конструктор, есть 2 конструктора:
public CyclicBarrier(int parties) {
this(parties, null);
}
public CyclicBarrier(int parties, Runnable barrierAction) {
if (parties <= 0) throw new IllegalArgumentException();
this.parties = parties;
this.count = parties;
this.barrierCommand = barrierAction;
}
Если вы используете CyclicBarrier, вы знаете, что CyclicBarrier поддерживает выполнение задачи потока, когда все потоки проходят через барьер.
Свойство partys — это количество потоков, это число используется для управления тем, когда следует отпустить открытое ограждение и разрешить все потоки. процесс прошел.
Ну, самый важный метод CyclicBarrier — это метод await, когда такой метод выполняется, это похоже на установку забора для блокировки потока, забор будет открыт только тогда, когда все потоки будут на заборе.
Посмотрите на реализацию этого метода.
реализация метода ожидания
Код аннотирован следующим образом:
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()) {
// 将损坏状态设置为 true
// 并通知其他阻塞在此栅栏上的线程
breakBarrier();
throw new InterruptedException();
}
// 获取下标
int index = --count;
// 如果是 0 ,说明到头了
if (index == 0) { // tripped
boolean ranAction = false;
try {
final Runnable command = barrierCommand;
// 执行栅栏任务
if (command != null)
command.run();
ranAction = true;
// 更新一代,将 count 重置,将 generation 重置.
// 唤醒之前等待的线程
nextGeneration();
// 结束
return 0;
} finally {
// 如果执行栅栏任务的时候失败了,就将栅栏失效
if (!ranAction)
breakBarrier();
}
}
for (;;) {
try {
// 如果没有时间限制,则直接等待,直到被唤醒
if (!timed)
trip.await();
// 如果有时间限制,则等待指定时间
else if (nanos > 0L)
nanos = trip.awaitNanos(nanos);
} catch (InterruptedException ie) {
// g == generation >> 当前代
// ! g.broken >>> 没有损坏
if (g == generation && ! g.broken) {
// 让栅栏失效
breakBarrier();
throw ie;
} else {
// 上面条件不满足,说明这个线程不是这代的.
// 就不会影响当前这代栅栏执行逻辑.所以,就打个标记就好了
Thread.currentThread().interrupt();
}
}
// 当有任何一个线程中断了,会调用 breakBarrier 方法.
// 就会唤醒其他的线程,其他线程醒来后,也要抛出异常
if (g.broken)
throw new BrokenBarrierException();
// g != generation >>> 正常换代了
// 一切正常,返回当前线程所在栅栏的下标
// 如果 g == generation,说明还没有换代,那为什么会醒了?
// 因为一个线程可以使用多个栅栏,当别的栅栏唤醒了这个线程,就会走到这里,所以需要判断是否是当前代。
// 正是因为这个原因,才需要 generation 来保证正确。
if (g != generation)
return index;
// 如果有时间限制,且时间小于等于0,销毁栅栏,并抛出异常
if (timed && nanos <= 0L) {
breakBarrier();
throw new TimeoutException();
}
}
} finally {
lock.unlock();
}
}
Хотя код длинный, общая логика очень проста. Подведем итоги метода.
-
Во-первых, у каждого CyclicBarrier есть Lock, который необходимо получить для выполнения метода await. Поэтому производительность CyclicBarrier в параллельных условиях невысока.
-
В некоторых суждениях о прерывании потока обратите внимание, что в CyclicBarrier прерывается только один поток, а остальные потоки также будут генерировать исключения прерывания. и,Этот CyclicBarrier нельзя использовать повторно.
-
Каждый раз, когда поток вызывает метод await, это означает, что поток достиг забора, тогда счетчик уменьшается на единицу. Если счетчик достигает 0, а это значит, что это последний поток генерации, достигший забора, попробуем выполнить задачу, введенную в нашем конструкторе. Наконец, генерация обновляется, счетчик сбрасывается и пробуждаются все потоки, которые ранее ждали на заборе.
-
Если не последний поток достиг забора, используйте метод await класса Condition, чтобы заблокировать поток. Если поток прерывается во время ожидания, генерируется исключение. Здесь обратите внимание, что если прерванный поток использует CyclicBarrier не из этого поколения, например, после того, как последний поток выполнит signalAll и обновит объект «поколение». В этом интервале поток прерывается, затем JDK считает, что задача выполнена, и ему не нужно заботиться о прерывании, просто отмечая его. Поэтому суждение else в catch используется для суждений, возникающих в редких случаях — задача выполнена, «генерация» обновляется, происходит внезапное прерывание. В настоящее время CyclicBarrier это не волнует. Потому что задача выполнена.
-
Когда один поток прерывается, он пробуждает другие потоки, поэтому необходимо оценить состояние прерывания.
-
Если поток пробуждается другим CyclicBarrier, то g должно быть равно генерации, и событие не может быть возвращено, а продолжает блокироваться в цикле. И наоборот, если текущий CyclicBarrier просыпается, возвращается индекс потока в CyclicBarrier. Совершил наезд на забор.
Суммировать
Из метода await CyclicBarrier относительно прост.Идея JDK заключается в настройке счетчика.Каждый раз, когда поток вызывает счетчик, он уменьшается на единицу, а Condition используется для блокировки потока. Когда счетчик равен 0, разбудить все потоки и попытаться выполнить задачу в конструкторе. Поскольку CyclicBarrier является повторяемым, счетчик необходимо сбросить.
Другим важным моментом CyclicBarrier является концепция генерации.Поскольку каждый поток может использовать несколько CyclicBarrier, и каждый CyclicBarrier может разбудить поток, он должен управляться генерацией.Если генерация не совпадает, он должен снова заснуть. В то же время это поколение также записывает состояние прерывания потока.Если какой-либо поток будет прерван, то все потоки вызовут исключение прерывания, и CyclicBarrier больше не будет доступен.
В целом, CyclicBarrier реализован с использованием счетчика, внутри которого находится переменная count, которая уменьшается на единицу при каждом вызове. Когда событие полного забора заканчивается, счетчик сбрасывается, чтобы его можно было использовать повторно.
Разница между ним и CountDownLatch в том, что CountDownLatch можно использовать только один раз, а CyclicBarrier можно использовать несколько раз.Можно сказать, что функции похожи, а CyclicBarrier мощнее. А CyclicBarrier несет в себе задачу, которую можно выполнить у забора. более гибкий.
Давайте сделаем снимок, чтобы рассказать о процессе CyclicBarrier. Похоже на CountDownLatch: