Параллелизм Java: защелка обратного отсчета против циклического барьера

160

Я читал через API java.util.concurrent и обнаружил, что

  • CountDownLatch: Средство синхронизации, позволяющее одному или нескольким потокам дождаться завершения набора операций, выполняемых в других потоках.
  • CyclicBarrier: Средство синхронизации, которое позволяет всем потокам ожидать друг друга, чтобы достичь общей барьерной точки.

Мне обоим кажется равным, но я уверен, что это намного больше.

Например, в CoundownLatch, the countdown value could not be reset, that can happen in the case of CyclicBarrier.

Есть ли другая разница между ними?
В каком месте use casesкто-то захочет сбросить значение обратного отсчета?

мечтатель
источник
12
Защелки предназначены для ожидания событий; барьеры для ожидания других потоков. - Java Concurrency in Practice, B.Goetz et al.
user2418306

Ответы:

137

Одно из основных отличий состоит в том, что CyclicBarrier выполняет (необязательную) Runnable задачу, которая запускается при выполнении общего барьерного условия.

Это также позволяет узнать количество клиентов, ожидающих на барьере, и количество, необходимое для запуска барьера. После срабатывания шлагбаум сбрасывается и может быть использован снова.

Для простых случаев использования - запуск служб и т. Д. CyclicBarrier полезен для более сложных задач координации. Примером такой вещи могут быть параллельные вычисления - где в вычислении участвуют несколько подзадач - что-то вроде MapReduce .

Джон
источник
6
«Это также позволяет получить количество клиентов, ожидающих на барьере, и количество, необходимое для запуска барьера. После запуска барьер сбрасывается и может быть использован снова». Мне очень нравится этот момент. Несколько статей, которые я прочитал, предположили, что CyclicBarrier является циклическим, потому что вы вызываете метод reset (). Это правда, но они не часто упоминают, что барьер автоматически сбрасывается, как только он срабатывает. Я опубликую пример кода, чтобы проиллюстрировать это.
Кевин Ли
@Kevin Lee Спасибо за «барьер автоматически сбрасывается, как только он срабатывает». поэтому нет необходимости вызывать reset () в коде.
сверхновая
134

Есть еще одно отличие.

При использовании a CyclicBarrierпредполагается, что вы указываете количество ожидающих потоков, которые запускают барьер. Если вы укажете 5, у вас должно быть как минимум 5 потоков для вызова await().

При использовании a CountDownLatchвы указываете количество вызовов, countDown()которые приведут к освобождению всех ожидающих потоков. Это означает, что вы можете использовать CountDownLatchтолько один поток.

«Зачем ты это делаешь?», Скажете вы. Представьте, что вы используете таинственный API, закодированный кем-то еще, который выполняет обратные вызовы. Вы хотите, чтобы один из ваших потоков ожидал, пока определенный обратный вызов не будет вызван несколько раз. Вы не представляете, в каких потоках будет вызываться обратный вызов. В этом случае a CountDownLatchидеально, тогда как я не могу придумать, как реализовать это с помощью a CyclicBarrier(на самом деле, я могу, но это связано с таймаутами ... хм!)

Я просто хочу, чтобы это CountDownLatchможно было сбросить!

Ким
источник
10
Я думаю, что это ответ, который лучше показать теоретические различия. Тот факт, что защелки можно сломать, просто вызвав метод многократно, в то время как барьерам требуется точное количество потоков для wait ().
flagg19
43
Правильно - вот главное отличие: CountDownLatch -> NumberOfCalls, CyclicBarrier -> NumberOfThreads
Иван
1
Я согласен, что было бы CountDownLatchнеплохо сбросить настройки - обходной путь, который я использую для реализации грубого уведомления об ожидании, - это просто обновить CountDownLatchсразу же, когда вводится защищенный кодовый блок (когда защелка достигает нуля). Конечно, это не применимо при любых обстоятельствах / масштабах, но я подумал, что стоит отметить, что это вариант в ситуациях с златовлаской шкурой.
Эфемера
2
Один из лучших ответов на эту тему. Java Concurrency in Practice- говорит то же самое Latches are for waiting for events; barriers are for waiting for other threads.. Основной и важный момент, чтобы понять разницу между этими двумя.
Рахул Дев Мишра
В документе Java 8 говорится, что «CountDownLatch, инициализированный для N, можно использовать, чтобы заставить один поток ждать, пока N потоков не выполнит какое-либо действие или какое-либо действие будет выполнено N раз». мне кажется: CountDownLatch -> NUMBEROFCALLS Или CountDownLatch -> NumberOfThreads
NIR
41

Один момент, который еще никто не упомянул, заключается в том, что CyclicBarrier, если у потока есть проблема (время ожидания, прервано ...), все остальные, которые достигли, await()получают исключение. Смотрите Javadoc:

CyclicBarrier использует модель обрыва «все или ничего» для неудачных попыток синхронизации: если поток преждевременно покидает точку барьера из-за прерывания, сбоя или тайм-аута, все другие потоки, ожидающие в этой точке барьера, также будут ненормально выходить через BrokenBarrierException (или InterruptedException если они тоже были прерваны примерно в одно и то же время).

Chirlo
источник
22

Я думаю, что JavaDoc объяснил различия явно. Большинство людей знают, что CountDownLatch не может быть сброшен, однако CyclicBarrier может. Но это не единственное отличие, иначе CyclicBarrier можно переименовать в ResetbleCountDownLatch. Мы должны рассказать о различиях с точки зрения их целей, которые описаны в JavaDoc.

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

CyclicBarrier: средство синхронизации, позволяющее всем потокам ожидать друг друга, чтобы достичь общей барьерной точки.

В countDownLatch есть один или несколько потоков, ожидающих завершения набора других потоков . В этой ситуации существует два типа потоков: один тип ожидает, другой тип что-то делает, после завершения своих задач они могут ожидать или просто завершиться.

В CyclicBarrier есть только один тип потоков, они ждут друг друга, они равны.

Джастин Сиви
источник
1
«В CyclicBarrier есть только один тип потоков» ... Они равны в своей «роли ожидания», пока другие потоки не вызовут .await (), но они могут быть «не равны в том, что они делают». Также все они должны быть абсолютно разными экземплярами потока (!) Одного и того же типа или разных типов, в то время как в CountDownLatch один и тот же поток может вызывать countDown () и влиять на результат.
Владимир Набоков
Я согласен, что CountDownLatch по своей природе требует двух ролей: один клиент для countDown и один клиент для ожидания. С другой стороны, клиенты CyclicBarrier могут нормально работать с методом await.
Исаолмез
14

Основное отличие задокументировано прямо в Javadocs для CountdownLatch. А именно:

CountDownLatch инициализируется с заданным количеством. Методы await блокируются до тех пор, пока текущий счетчик не достигнет нуля из-за вызовов метода countDown (), после чего все ожидающие потоки освобождаются и любые последующие вызовы await немедленно возвращаются. Это одноразовое явление - счет не может быть сброшен. Если вам нужна версия, которая сбрасывает счет, рассмотрите возможность использования CyclicBarrier.

Источник 1.6 Javadoc

JohnnyO
источник
4
Если их различие может быть просто сброшено или нет, CyclicBarrier может быть лучше назван ResetableCountDownLatch, который является более значимым из-за разницы.
James.Xu
12

CountDownLatch используется для одноразовой синхронизации. При использовании CountDownLatch любой поток может вызывать countDown () столько раз, сколько пожелает. Потоки, вызвавшие await (), блокируются до тех пор, пока счетчик не достигнет нуля из-за вызовов countDown () другими неблокированными потоками. Javadoc для CountDownLatch состояний:

Методы await блокируются до тех пор, пока текущий счетчик не достигнет нуля из-за вызовов метода countDown (), после чего все ожидающие потоки освобождаются и любые последующие вызовы await немедленно возвращаются. ...

Другое типичное использование - разделить проблему на N частей, описать каждую часть с помощью Runnable, который выполняет эту часть и выполняет обратный отсчет в защелке, и поставить все Runnables в очередь для исполнителя. Когда все части будут готовы, координирующий поток сможет пройти через await. (Когда потоки должны повторно вести обратный отсчет таким образом, вместо этого используйте CyclicBarrier.)

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

Барьер называется циклическим, поскольку его можно использовать повторно после освобождения ожидающих потоков.

В отличие от CountDownLatch, каждый вызов await () относится к некоторой фазе и может привести к блокировке потока, пока все стороны, принадлежащие этой фазе, не вызовут await (). Нет явной операции countDown (), поддерживаемой CyclicBarrier.

Шамс
источник
12

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

Чтобы проиллюстрировать поведение циклического барьера, я сделал несколько примеров кода. Как только барьер опрокидывается, он автоматически сбрасывается, чтобы его можно было использовать снова (следовательно, он является «циклическим»). Когда вы запускаете программу, обратите внимание, что распечатки «Let's play» запускаются только после того, как барьер опрокинут.

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

public class CyclicBarrierCycles {

    static CyclicBarrier barrier;

    public static void main(String[] args) throws InterruptedException {
        barrier = new CyclicBarrier(3); 

        new Worker().start();
        Thread.sleep(1000);
        new Worker().start();
        Thread.sleep(1000);
        new Worker().start();
        Thread.sleep(1000);

        System.out.println("Barrier automatically resets.");

        new Worker().start();
        Thread.sleep(1000);
        new Worker().start();
        Thread.sleep(1000);
        new Worker().start();
    }

}


class Worker extends Thread {
    @Override
    public void run() {
        try {
            CyclicBarrierCycles.barrier.await();
            System.out.println("Let's play.");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (BrokenBarrierException e) {
            e.printStackTrace();
        }
    }
}
Кевин Ли
источник
8

Когда я изучал защелки и циклические барьеры, я придумал эту метафору. Циклические барьеры . Представьте, что в компании есть комната для переговоров. Чтобы начать собрание, на собрание должно прийти определенное количество участников (чтобы сделать его официальным). ниже приведен код обычного участника собрания (сотрудника)

class MeetingAtendee implements Runnable {

CyclicBarrier myMeetingQuorumBarrier;

public MeetingAtendee(CyclicBarrier myMileStoneBarrier) {
    this.myMeetingQuorumBarrier = myMileStoneBarrier;
}

@Override
public void run() {
    try {
        System.out.println(Thread.currentThread().getName() + " i joined the meeting ...");
        myMeetingQuorumBarrier.await();
        System.out.println(Thread.currentThread().getName()+" finally meeting stared ...");
    } catch (InterruptedException e) {
        e.printStackTrace();
    } catch (BrokenBarrierException e) {
        System.out.println("Meeting canceled! every body dance <by chic band!>");
    }
 }
}

сотрудник присоединяется к собранию, ждет, пока другие придут, чтобы начать собрание. кроме того, он выходит, если собрание отменяется :) тогда у нас есть БОСС, как дозы не любят ждать появления других, и если он теряет своего пациента, он отменяет собрание.

class MeetingAtendeeTheBoss implements Runnable {

CyclicBarrier myMeetingQuorumBarrier;

public MeetingAtendeeTheBoss(CyclicBarrier myMileStoneBarrier) {
    this.myMeetingQuorumBarrier = myMileStoneBarrier;
}

@Override
public void run() {
    try {
        System.out.println(Thread.currentThread().getName() + "I am THE BOSS - i joined the meeting ...");
        //boss dose not like to wait too much!! he/she waits for 2 seconds and we END the meeting
        myMeetingQuorumBarrier.await(1,TimeUnit.SECONDS);
        System.out.println(Thread.currentThread().getName()+" finally meeting stared ...");
    } catch (InterruptedException e) {
        e.printStackTrace();
    } catch (BrokenBarrierException e) {
        System.out.println("what WHO canceled The meeting");
    } catch (TimeoutException e) {
        System.out.println("These employees waste my time!!");
    }
 }
}

В обычный день сотрудник приходит на собрание, ожидая появления других, и если некоторые посетители не приходят, им приходится ждать бесконечно! на каком-то специальном собрании приходит начальник, и он не любит ждать. (5 человек должны начать совещание, но приходит только начальник, а также служащий с энтузиазмом), поэтому он отменяет собрание (сердито)

CyclicBarrier meetingAtendeeQuorum = new CyclicBarrier(5);
Thread atendeeThread = new Thread(new MeetingAtendee(meetingAtendeeQuorum));
Thread atendeeThreadBoss = new Thread(new MeetingAtendeeTheBoss(meetingAtendeeQuorum));
    atendeeThread.start();
    atendeeThreadBoss.start();

Вывод:

//Thread-1I am THE BOSS - i joined the meeting ...
// Thread-0 i joined the meeting ...
// These employees waste my time!!
// Meeting canceled! every body dance <by chic band!>

Существует еще один сценарий, в котором другой внешний поток (землетрясение) отменяет собрание (метод сброса вызова). в этом случае все ожидающие потоки просыпаются в результате исключения.

class NaturalDisasters implements Runnable {

CyclicBarrier someStupidMeetingAtendeeQuorum;

public NaturalDisasters(CyclicBarrier someStupidMeetingAtendeeQuorum) {
    this.someStupidMeetingAtendeeQuorum = someStupidMeetingAtendeeQuorum;
}

void earthQuakeHappening(){
    System.out.println("earth quaking.....");
    someStupidMeetingAtendeeQuorum.reset();
}

@Override
public void run() {
    earthQuakeHappening();
 }
}

выполнение кода приведет к забавному выводу:

// Thread-1I am THE BOSS - i joined the meeting ...
// Thread-0 i joined the meeting ...
// earth quaking.....
// what WHO canceled The meeting
// Meeting canceled! every body dance <by chic band!>

Вы также можете добавить секретаря в комнату для собраний, если она проводится, она регистрирует все, но не участвует в собрании:

class MeetingSecretary implements Runnable {

@Override
public void run() {
        System.out.println("preparing meeting documents");
        System.out.println("taking notes ...");
 }
}

Защелки : если злой начальник хочет провести выставку для клиентов компании, все должно быть готово (ресурсы). мы предоставляем список дел каждому рабочему (Thread), дозирующему его работу, и мы проверяем список дел (некоторые рабочие рисуют, другие готовят звуковую систему ...). Когда все пункты в списке дел завершены (ресурсы предоставлены), мы можем открыть двери для клиентов.

public class Visitor implements Runnable{

CountDownLatch exhibitonDoorlatch = null;

public Visitor (CountDownLatch latch) {
    exhibitonDoorlatch  = latch;
}

public void run() {
    try {
        exhibitonDoorlatch .await();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }

    System.out.println("customer visiting exebition");
 }
}

А рабочие как готовят выставку

class Worker implements Runnable {

CountDownLatch myTodoItem = null;

public Worker(CountDownLatch latch) {
    this.myTodoItem = latch;
}

public void run() {
        System.out.println("doing my part of job ...");
        System.out.println("My work is done! remove it from todo list");
        myTodoItem.countDown();
 }
}

    CountDownLatch preperationTodoList = new CountDownLatch(3);

    // exhibition preparation workers  
    Worker      electricalWorker      = new Worker(preperationTodoList);
    Worker      paintingWorker      = new Worker(preperationTodoList);

    // Exhibition Visitors 
    ExhibitionVisitor exhibitionVisitorA = new ExhibitionVisitor(preperationTodoList);
    ExhibitionVisitor exhibitionVisitorB = new ExhibitionVisitor(preperationTodoList);
    ExhibitionVisitor exhibitionVisitorC = new ExhibitionVisitor(preperationTodoList);

    new Thread(electricalWorker).start();
    new Thread(paintingWorker).start();

    new Thread(exhibitionVisitorA).start();
    new Thread(exhibitionVisitorB).start();
    new Thread(exhibitionVisitorC).start();
Mr.Q
источник
7

В двух словах , просто чтобы понять ключевые функциональные различия между ними:

public class CountDownLatch {
    private Object mutex = new Object();
    private int count;

    public CountDownLatch(int count) {
        this.count = count;
    }

    public void await() throws InterruptedException {
        synchronized (mutex) {
            while (count > 0) {
                mutex.wait();
            }
        }
    }

    public void countDown() {
        synchronized (mutex) {
            if (--count == 0)
                mutex.notifyAll();
        }

    }
}

и

public class CyclicBarrier {
    private Object mutex = new Object();
    private int count;

    public CyclicBarrier(int count) {
        this.count = count;
    }

    public void await() throws InterruptedException {
        synchronized (mutex) {
            count--;
            while(count > 0)
                mutex.wait();
            mutex.notifyAll();
        }
    }
}

кроме, конечно, таких функций, как неблокирование, временное ожидание, диагностика и все, что было подробно описано в ответах выше.

Вышеупомянутые классы, однако, полностью функциональны и эквивалентны, в пределах предоставленной функциональности, их соответствующим однофамильцам.

С другой стороны, CountDownLatchвнутренние классы являются подклассами AQS, хотя и CyclicBarrierиспользуют ReentrantLock(я подозреваю, что это может быть другой путь, или оба могут использовать AQS или оба используют Lock - без потери эффективности производительности)

igor.zh
источник
5

Одно очевидное отличие состоит в том, что только N потоков могут ожидать на циклическом барьере N освобождение за один цикл. Но неограниченное количество потоков может ожидать в CountDownLatch из N. Уменьшение обратного отсчета может быть выполнено одним потоком N раз или N потоками по одному разу или в каждой комбинации.

Pramma
источник
4

В случае CyclicBarrier, как только ВСЕ дочерние потоки начинают вызывать барьер .await (), Runnable выполняется в Барьере. Для завершения барьера.await в каждом дочернем потоке требуется разное время, и все они завершаются в одно и то же время.

Brandon
источник
4

В CountDownLatch основные потоки ожидают завершения других потоков. В CyclicBarrier рабочие потоки ждут друг друга для завершения своего выполнения.

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

V Джо
источник
Это не должно быть основным потоком. Это может быть любой поток, который создает CountDownLatch и делится им с другими неосновными потоками.
Аникет Тхакур
1

CountDownLatch - это обратный отсчет чего угодно; CyclicBarrier обратный отсчет только для потока

Предположим, что есть 5 рабочих нитей и одна нить грузоотправителя, а когда рабочие изготовят 100 изделий, грузоотправитель отправит их.

Для CountDownLatch счетчик может быть на рабочих или предметах

Для CyclicBarrier счетчик может только на рабочих

Если рабочий засыпает бесконечно, с CountDownLatch на предметах, Грузоотправитель может отправить; Тем не менее, с CyclicBarrier, Shipper никогда не может быть вызван

yk42b
источник
0

@Kevin Lee и @Jon Я попробовал CyclicBarrier с опциональным Runnable. Похоже, что он запускается в начале и после чаевых CyclicBarrier. Вот код и вывод

статический барьер CyclicBarrier;

    public static void main(String[] args) throws InterruptedException {
        barrier = new CyclicBarrier(3, new Runnable() {
            @Override
            public void run() {
                System.out.println("I run in the beginning and after the CyclicBarrier is tipped");
            }
        });

        new Worker().start();
        Thread.sleep(1000);
        new Worker().start();
        Thread.sleep(1000);
        new Worker().start();
        Thread.sleep(1000);

        System.out.println("Barrier automatically resets.");

        new Worker().start();
        Thread.sleep(1000);
        new Worker().start();
        Thread.sleep(1000);
        new Worker().start();
    }

Вывод

I run in the beginning and after the CyclicBarrier is tipped
Let's play.
Let's play.
Let's play.
Barrier automatically resets.
I run in the beginning and after the CyclicBarrier is tipped
Let's play.
Let's play.
Let's play.
Хари Рао
источник