Параллелизм Java: блокировка обратного отсчета против циклического барьера - PullRequest
149 голосов
/ 12 ноября 2010

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

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

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

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

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

Ответы [ 14 ]

124 голосов
/ 12 ноября 2010

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

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

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

117 голосов
/ 01 марта 2011

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

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

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

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

Я просто хочу, чтобы CountDownLatch мог быть сброшен!

38 голосов
/ 07 октября 2012

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

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

20 голосов
/ 02 июля 2014

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

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

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

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

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

14 голосов
/ 12 ноября 2010

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

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

source 1.6 Javadoc

12 голосов
/ 12 апреля 2014

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

Чтобы проиллюстрировать поведение циклического барьера, я сделал несколько примеров кода. Как только шлагбаум опрокидывается, он автоматически сбрасывается, чтобы его можно было использовать снова (следовательно, он «циклический»). Когда вы запустите программу, обратите внимание, что распечатки «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();
        }
    }
}
10 голосов
/ 30 апреля 2012

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

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

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

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

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

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

7 голосов
/ 09 августа 2017

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

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();
6 голосов
/ 24 января 2017

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

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 - без потери эффективности производительности)

4 голосов
/ 29 октября 2015

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

...