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


Я читал через java.утиль.параллельный API, и установлено, что

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

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

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

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

12 137

12 ответов:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

обратный отсчет инициализируется с помощью учитывая количество. Ожидания методами до тех пор, пока текущее значение счетчика достигает нуля из-за вызовов обратного отсчета() метод, после которого все ждут потоки освобождаются и любые последующие вызовы ждут возвращения немедленно. Это один выстрел феномен-граф не может быть сброс. Если вам нужна версия что сбрасывает счетчик, рассмотрите возможность использования 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 любой поток может вызывать countDown () столько раз, сколько ему нравится. Потоки, которые вызвали await (), блокируются до тех пор, пока счетчик не достигнет нуля из-за вызовов countDown () другими разблокированными потоками. Элемент javadoc для CountDownLatch гласит:

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

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

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

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

В отличие от обратного отсчета, каждый вызов 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 или оба используют блокировку - без потери эффективности работы)

когда я изучал защелки и циклические баррикады, я придумал эти метафоры. 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 потоков могут ожидать на циклическом барьере N, чтобы быть выпущенными за один цикл. Но неограниченное количество потоков может ожидать обратного отсчета N. декремент обратного отсчета может быть выполнен одним потоком N раз или N потоков один раз каждый или комбинации.

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

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