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

Я читал через java.утиль.количество одновременных API, и установлено, что

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

12 ответов


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

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

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


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

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

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

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

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


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

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


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

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

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

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

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


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

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

источник 1.6 Javadoc


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

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

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

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

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

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

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

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


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

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


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

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 ...");
 }
}

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

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();

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


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


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

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