Cyclicbarrier помогает наладить взаимодействие потоков.
Например, есть трехэтапная задача, которая выполняется в 10 рук (потоков). Сначала надо оторвать обои, потом намазать клеем, потом приклеить. То есть метод run() состоит из трех действий:
public class Worker implements Runnable{
@Override
public void run() {
System.out.println("оторвать");
System.out.println("намазать");
System.out.println("приклеить");
}
}
К сожалению, если просто запустить 10 потоков, то действия будут выполнены вразнобой: один поток уже приклеил, второй еще отрывает, третий намазывает и т.д.:
оторвать оторвать намазать оторвать намазать ... Всего 30 строк
А нужно, чтобы первые 10 строк были «оторвать», вторые 10 строк — «намазать», а третьи — «приклеить».
Вариант с CyclicBarrier
Для этого в нужных местах проставляется CyclicBarrier. Точнее, у нас даже два CyclicBarrier — они ставятся в тех местах, где поток должен приостановиться дождаться остальных потоков для продолжения работы. У нас это после «оторвать» и после «намазать» — сначала все рабочие отрывают обои, ждут друг друга, и только потом приступают к следующему этапу «намазать». Аналогично после «намазать» каждый останавливается и ждет, когда остальные 9 закончат мазать, и после этого все продолжают.
Делается это с помощью команды cyclicBarrier.await():
public class Worker implements Runnable{
private final CyclicBarrier b1;
private final CyclicBarrier b2;
public Worker(CyclicBarrier b1, CyclicBarrier b2){
this.b1=b1;
this.b2=b2;
}
@Override
public void run() {
try {
System.out.println("оторвать");
b1.await();
System.out.println("намазать");
b2.await();
System.out.println("приклеить");
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
}
}
Команда выбрасывает исключение, поэтому код окружен try/catch.
При создании CyclicBarrier указывается, сколько потоков участвуют во взаимном ожидании:
CyclicBarrier b1=new CyclicBarrier(10);
Таким образом, весь код с созданием двух CyclicBarrier и запуском 10 потоков выглядит так:
public class Worker implements Runnable{
private final CyclicBarrier b1;
private final CyclicBarrier b2;
public Worker(CyclicBarrier b1, CyclicBarrier b2){
this.b1=b1;
this.b2=b2;
}
@Override
public void run() {
try {
System.out.println("оторвать");
b1.await();
System.out.println("намазать");
b2.await();
System.out.println("приклеить");
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
CyclicBarrier b1=new CyclicBarrier(10);
CyclicBarrier b2=new CyclicBarrier(10);
IntStream.range(0,10).forEach((i)->
new Thread(new Worker(b1,b2)).start());
}
}
Теперь вывод в консоль выглядит последовательно:
оторвать оторвать ...10 раз намазать намазать ...10 раз приклеить приклеить ...10 раз
CyclicBarrier с barrierAction
Еще при создании барьера можно указать действие, которое выполнится перед перешагиванием барьера. Например, когда обои оторвали, напишем «1 этап закончен», а когда все намазали, напишем «2 этап закончен».
Это действие (в нашем случае вывод в консоль) указывается во втором аргументе конструктора CyclicBarrier в интерфейсе Runnable (первый аргумент, как вы помните — число потоков).
CyclicBarrier b1=new CyclicBarrier(10, ()->{
System.out.println("1 этап закончен");
});
CyclicBarrier b2=new CyclicBarrier(10, ()->{
System.out.println("2 этап закончен");
});
Теперь результат будет такой:
оторвать оторвать ...10 раз 1 этап закончен намазать намазать ...10 раз 2 этап закончен приклеить приклеить ...10 раз
Итоги
Исходный код примера есть на GitHub. О CountDownLatch читайте тут.
Барьер не зря назван циклическим и после обнуления им можно воспользоваться заново.
Т.е. в примере b2.await(); можно заменить на b1.await();
Верное замечание, спасибо. Единственное, вывод в консоль тогда после второго этапа не поменять, только ради этого b2.