Współbieżność Java: zatrzask odliczania a bariera cykliczna

160

Czytałem przez interfejs API java.util.concurrent i znalazłem to

  • CountDownLatch: Pomoc w synchronizacji, która pozwala jednemu lub większej liczbie wątków czekać na zakończenie zestawu operacji wykonywanych w innych wątkach.
  • CyclicBarrier: Pomoc w synchronizacji, która umożliwia zestawowi wątków czekanie na siebie nawzajem w celu osiągnięcia wspólnego punktu bariery.

Wydaje mi się, że oba wydają mi się równe, ale jestem pewien, że jest w tym znacznie więcej.

Na przykład w CoundownLatch, the countdown value could not be reset, that can happen in the case of CyclicBarrier.

Czy jest między nimi jakaś inna różnica?
Jakie są miejsca, w use casesktórych ktoś chciałby zresetować wartość odliczania?

marzyciel
źródło
12
Zatrzaski czekają na wydarzenia; bariery czekają na inne wątki. - Współbieżność Java w praktyce, B.Goetz et al.
user2418306

Odpowiedzi:

137

Jedną z głównych różnic jest to, że CyclicBarrier przyjmuje (opcjonalne) Runnable zadanie, które jest uruchamiane po spełnieniu wspólnego warunku bariery.

Pozwala również uzyskać liczbę klientów oczekujących przy szlabanie oraz liczbę wymaganą do uruchomienia bariery. Po uruchomieniu bariera jest resetowana i może być ponownie użyta.

W prostych przypadkach użycia - uruchamianie usług itp ... CountdownLatch jest w porządku. CyclicBarrier jest przydatny w przypadku bardziej złożonych zadań koordynacyjnych. Przykładem takiej rzeczy mogą być obliczenia równoległe - w których w obliczeniach zaangażowanych jest wiele podzadań - coś w rodzaju MapReduce .

Jon
źródło
6
„Pozwala również uzyskać liczbę klientów oczekujących przy szlabanie oraz liczbę wymaganą do uruchomienia bariery. Po uruchomieniu bariera jest resetowana i może być ponownie użyta”. Bardzo podoba mi się ten punkt. Kilka artykułów, które przeczytałem, sugerowało, że CyclicBarrier jest cykliczny, ponieważ wywołujesz metodę reset (). To prawda, ale często nie wspominają, że bariera resetuje się automatycznie, gdy tylko zostanie uruchomiona. Zamieszczę przykładowy kod, aby to zilustrować.
Kevin Lee,
@Kevin Lee Dzięki za „barierę resetuje się automatycznie, gdy tylko zostanie uruchomiona”. więc nie ma potrzeby wywoływania funkcji reset () w kodzie.
supernowa
134

Jest inna różnica.

Używając a CyclicBarrier, zakłada się, że określasz liczbę oczekujących wątków, które wyzwalają barierę. Jeśli określisz 5, musisz mieć co najmniej 5 wątków do wywołania await().

Używając a CountDownLatch, określasz liczbę wywołań, countDown()które spowodują zwolnienie wszystkich oczekujących wątków. Oznacza to, że możesz użyć CountDownLatchtylko z jednym wątkiem.

Możesz powiedzieć: „Dlaczego miałbyś to zrobić?”. Wyobraź sobie, że używasz tajemniczego interfejsu API zakodowanego przez kogoś innego, który wykonuje wywołania zwrotne. Chcesz, aby jeden z Twoich wątków czekał, aż określone wywołanie zwrotne zostanie wywołane kilka razy. Nie masz pojęcia, w których wątkach zostanie wywołane wywołanie zwrotne. W tym przypadku a CountDownLatchjest idealne, podczas gdy nie mogę wymyślić żadnego sposobu zaimplementowania tego za pomocą a CyclicBarrier(właściwie mogę, ale wymaga to przekroczenia limitu czasu ... fuj!).

Chciałbym tylko, żeby CountDownLatchmożna było to zresetować!

Kim
źródło
10
Myślę, że to jest odpowiedź, która lepiej pokazuje teoryczne różnice. Fakt, że zatrzaski można złamać, po prostu wywołując wiele razy metodę, podczas gdy bariery wymagają dokładnej liczby wątków, aby czekać ().
flagg19
43
Racja - to główna różnica: CountDownLatch -> NumberOfCalls, CyclicBarrier -> NumberOfThreads
Ivan Voroshilin
1
Zgadzam się, że byłoby wspaniale, gdyby można CountDownLatchbyło je zresetować - obejściem, którego używam do implementacji zgrubnego powiadomienia o oczekiwaniu, jest po prostu ponowne uruchomienie CountDownLatchnatychmiast po wprowadzeniu chronionego bloku kodu (kiedy zatrzask osiągnie zero). Oczywiście nie ma to zastosowania we wszystkich okolicznościach / zakresach, ale pomyślałem, że warto zauważyć, że jest to opcja w sytuacjach złocistych.
Ephemera
2
Jedna z najlepszych odpowiedzi na ten temat. Java Concurrency in Practice- mówi to samo: Latches are for waiting for events; barriers are for waiting for other threads.. Podstawowy i niezbędny punkt do zrozumienia różnicy między tymi dwoma.
Rahul Dev Mishra
Dokument Java 8 mówi, że „CountDownLatch zainicjowany na N może być użyty do tego, aby jeden wątek czekał, aż N wątków zakończy jakąś akcję lub jakaś akcja zostanie zakończona N razy”. wydaje mi się: CountDownLatch -> NumberOfCalls lub CountDownLatch -> NumberOfThreads
nir
41

Jedną kwestią, o której nikt jeszcze nie wspomniał, jest to, że CyclicBarrierjeśli wątek ma problem (przekroczenie limitu czasu, przerwanie ...), wszystkie inne, które osiągnęły, await()otrzymują wyjątek. Zobacz Javadoc:

CyclicBarrier wykorzystuje model przerwania typu `` wszystko albo nic '' dla nieudanych prób synchronizacji: Jeśli wątek opuści punkt bariery przedwcześnie z powodu przerwania, awarii lub przekroczenia limitu czasu, wszystkie inne wątki oczekujące w tym punkcie bariery również wyjdą nienormalnie za pośrednictwem BrokenBarrierException (lub InterruptedException jeśli im również przerwano mniej więcej w tym samym czasie).

Chirlo
źródło
22

Myślę, że JavaDoc wyraźnie wyjaśnił różnice. Większość ludzi wie, że CountDownLatch nie może zostać zresetowany, jednak CyclicBarrier tak. Ale to nie jedyna różnica, lub można zmienić nazwę CyclicBarrier na ResetbleCountDownLatch. Różnice powinniśmy rozróżnić z perspektywy ich celów, które są opisane w JavaDoc

CountDownLatch: pomoc w synchronizacji, która umożliwia co najmniej jednemu wątkowi oczekiwanie na zakończenie zestawu operacji wykonywanych w innych wątkach.

CyclicBarrier: pomoc w synchronizacji, która umożliwia zestawowi wątków czekanie na siebie nawzajem do osiągnięcia wspólnego punktu bariery.

W countDownLatch istnieje co najmniej jeden wątek, który czeka na zakończenie zestawu innych wątków . W tej sytuacji są dwa typy wątków, jeden typ czeka, inny coś robi, po zakończeniu zadań mogą czekać lub po prostu zostać zakończone.

W CyclicBarrier jest tylko jeden rodzaj wątków, czekają na siebie, są sobie równe.

Justin Civi
źródło
1
„W CyclicBarrier istnieje tylko jeden typ wątków”… Są one równe w „roli oczekiwania”, aż inne wątki wywołają .await (), ale mogą być „nie równe w tym, co robią”. Ponadto wszystkie muszą być absolutnie różnymi instancjami wątku (!) Tego samego typu lub różnych typów, podczas gdy w CountDownLatch ten sam wątek może wywołać funkcję countDown () i wpłynąć na wynik.
Vladimir Nabokov
Zgadzam się, że CountDownLatch z natury wymaga dwóch ról: jednego klienta dla countDown i jednego klienta dla await. Z drugiej strony klienci CyclicBarrier mogą dobrze działać z metodą await.
isaolmez
14

Główna różnica jest udokumentowana bezpośrednio w Javadocs dla CountdownLatch. Mianowicie:

CountDownLatch jest inicjowany z podaną liczbą. Metody await blokują się do momentu, gdy bieżąca liczba osiągnie zero w wyniku wywołań metody countDown (), po czym wszystkie oczekujące wątki zostaną zwolnione, a kolejne wywołania await zostaną natychmiast zwrócone. Jest to jednorazowe zjawisko - licznika nie można zresetować. Jeśli potrzebujesz wersji, która resetuje licznik, rozważ użycie CyclicBarrier.

źródło 1.6 Javadoc

JohnnyO
źródło
4
Jeśli ich różnica może być po prostu zresetowana lub nie, CyclicBarrier może być lepiej nazwana ResetableCountDownLatch, co jest bardziej znaczące ze względu na różnicę.
James.Xu
12

CountDownLatch służy do jednorazowej synchronizacji. Podczas korzystania z CountDownLatch każdy wątek może wywoływać funkcję countDown () dowolną liczbę razy. Wątki, które wywołały await (), są blokowane do zera z powodu wywołań funkcji countDown () przez inne niezablokowane wątki. Plik javadoc dla CountDownLatch stwierdza:

Metody await blokują się do momentu, gdy bieżąca liczba osiągnie zero w wyniku wywołań metody countDown (), po czym wszystkie oczekujące wątki zostaną zwolnione, a kolejne wywołania await zostaną natychmiast zwrócone. ...

Innym typowym zastosowaniem byłoby podzielenie problemu na N części, opisanie każdej części za pomocą Runnable, które wykonuje tę część i odlicza w dół na zatrzasku, i ustawia w kolejce wszystkie Runnable do Executora. Kiedy wszystkie części podrzędne zostaną ukończone, wątek koordynujący będzie mógł przejść przez czekanie. (Gdy wątki muszą wielokrotnie odliczać w ten sposób, zamiast tego użyj CyclicBarrier).

W przeciwieństwie do tego cykliczna bariera jest używana dla wielu punktów synchronizacji, np. Jeśli zestaw wątków wykonuje obliczenia pętlowe / fazowe i wymaga synchronizacji przed rozpoczęciem następnej iteracji / fazy. Zgodnie z javadoc dla CyclicBarrier :

Bariera nazywana jest cykliczną, ponieważ można ją ponownie wykorzystać po zwolnieniu oczekujących wątków.

W przeciwieństwie do CountDownLatch, każde wywołanie await () należy do jakiejś fazy i może powodować blokowanie wątku do momentu, gdy wszystkie strony należące do tej fazy wywołają await (). Nie ma jawnej operacji countDown () obsługiwanej przez parametr CyclicBarrier.

pozory
źródło
12

Na to pytanie udzielono już wystarczającej odpowiedzi, ale myślę, że mogę trochę dodać wartość, zamieszczając jakiś kod.

Aby zilustrować zachowanie cyklicznej bariery, stworzyłem przykładowy kod. Gdy tylko szlaban jest przechylony, jest on automatycznie resetowany, aby można go było ponownie użyć (stąd jest „cykliczny”). Po uruchomieniu programu zwróć uwagę, że wydruki „Zagrajmy” są wyzwalane dopiero po przechyleniu bariery.

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();
        }
    }
}
Kevin Lee
źródło
8

Kiedy uczyłem się o zatrzaskach i cyklicznych barierach, wymyśliłem te metafory. cykliczne bariery : Wyobraź sobie, że firma ma salę konferencyjną. Aby rozpocząć spotkanie, pewna liczba uczestników spotkania musi przyjść na spotkanie (aby było ono oficjalne). poniżej znajduje się kod zwykłego uczestnika spotkania (pracownika)

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

pracownik dołącza do spotkania, czeka, aż inni przyjdą, aby rozpocząć spotkanie. też wychodzi, jeśli spotkanie zostanie odwołane :) wtedy mamy SZEFA, jak dawki nie lubią czekać, aż pojawią się inni, a jeśli straci pacjenta, odwołuje spotkanie.

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

W normalnym dniu pracownik przychodzi na spotkanie, czeka na pojawienie się innego, a jeśli niektórzy uczestnicy nie przychodzą, muszą czekać bez końca! na jakimś specjalnym spotkaniu szef przychodzi i nie lubi czekać. (5 osób musi zacząć spotkanie, ale przychodzi tylko szef i entuzjastyczny pracownik), więc odwołuje spotkanie (ze złością)

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

Wynik:

//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!>

Istnieje inny scenariusz, w którym inny wątek z zewnątrz (trzęsienie ziemi) odwołuje spotkanie (metoda resetowania połączenia). w tym przypadku wszystkie oczekujące wątki zostaną obudzone przez wyjątek.

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

uruchomiony kod spowoduje zabawne wyjście:

// 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!>

Możesz także dodać sekretarkę do pokoju konferencyjnego, jeśli odbędzie się spotkanie, będzie dokumentować wszystko, ale nie jest częścią spotkania:

class MeetingSecretary implements Runnable {

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

Zamki : jeśli wściekły szef chce zorganizować wystawę dla klientów firmy, wszystko musi być gotowe (zasoby). tworzymy listę rzeczy do zrobienia, każdy pracownik (Thread) dozuje swoją pracę i sprawdzamy listę rzeczy do zrobienia (jedni malują, inni przygotowują nagłośnienie ...). Kiedy wszystkie pozycje na liście zadań są kompletne (zasoby są dostępne), możemy otworzyć drzwi klientom.

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

A jak robotnicy przygotowują wystawę:

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();
Panie Q
źródło
7

Krótko mówiąc , wystarczy zrozumieć kluczowe różnice funkcjonalne między nimi:

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

    }
}

i

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

z wyjątkiem, oczywiście, funkcji takich jak nieblokowanie, czasowe oczekiwanie, diagnostyka i wszystko, co zostało szczegółowo wyjaśnione w powyższych odpowiedziach.

Powyższe klasy są jednak w pełni funkcjonalne i równoważne, w ramach dostarczonej funkcjonalności, swoim korespondentom.

Na innym uwaga, CountDownLatch„s wewnętrzne podklasy klasy AQS, natomiast CyclicBarrierzastosowania ReentrantLock(moje podejrzenie, że może być odwrotnie lub oba mogą korzystać zarówno AQS lub wykorzystanie lock - bez utraty wydajności wydajności)

igor.zh
źródło
5

Jedną oczywistą różnicą jest to, że tylko N wątków może czekać na CyclicBarrier z N do uwolnienia w jednym cyklu. Ale nieograniczona liczba wątków może oczekiwać na CountDownLatch o wartości N. Odliczanie w dół można wykonać o jeden wątek N razy lub N wątków jeden raz lub kombinacje.

Pramma
źródło
4

W przypadku CyclicBarrier, gdy tylko WSZYSTKIE wątki potomne zaczną wywoływać barrier.await (), Runnable jest wykonywany w Barrierze. Bariera. Czekanie w każdym dziecięcym wątku zajmie różną długość czasu, aby się skończyć i wszystkie kończą się w tym samym czasie.

Brandon
źródło
4

W CountDownLatch główne wątki czekają na zakończenie wykonywania przez inne wątki. W CyclicBarrier wątki robocze czekają na siebie nawzajem na zakończenie wykonywania.

Nie możesz ponownie użyć tego samego CountDownLatch instancji gdy liczba osiągnie zero i zatrzask jest otwarty, z drugiej strony CyclicBarrier można ponownie wykorzystać, resetując barierę, gdy bariera zostanie złamana.

V Jo
źródło
Nie musi to być główny wątek. Może to być dowolny wątek, który tworzy CountDownLatch i współdzieli go z innymi wątkami innymi niż główne.
Aniket Thakur
1

CountDownLatch to odliczanie wszystkiego; CyclicBarrier jest odliczaniem tylko dla wątku

Załóżmy, że jest 5 wątków roboczych i jeden wątek wysyłający, a gdy pracownicy wyprodukują 100 przedmiotów, nadawca je wyśle.

W przypadku CountDownLatch licznik może znajdować się na pracownikach lub przedmiotach

W przypadku CyclicBarrier licznik może działać tylko na robotnikach

Jeśli pracownik zasypia w nieskończoność, z CountDownLatch na przedmiotach, Shipper może wysłać; Jednak w przypadku CyclicBarrier nigdy nie można wywołać nadawcy

yk42b
źródło
0

@Kevin Lee i @Jon Próbowałem CyclicBarrier z opcjonalnym Runnable. Wygląda na to, że działa na początku i po przechyleniu CyclicBarrier. Oto kod i dane wyjściowe

statyczna bariera 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();
    }

Wynik

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.
Hari Rao
źródło