Java eşzamanlılığı: Geri sayım mandalı ve Döngüsel bariyer


160

Java.util.concurrent API üzerinden okuyordum ve

  • CountDownLatch: Bir veya daha fazla iş parçacığının, diğer iş parçacıklarında gerçekleştirilen bir dizi işlem tamamlanana kadar beklemesine izin veren bir senkronizasyon yardımcısı.
  • CyclicBarrier: Bir dizi ipliğin birbirinin ortak bir bariyer noktasına ulaşmasını beklemesine izin veren bir senkronizasyon yardımcısı.

Bana göre her ikisi de eşit gözüküyor, ama eminim bundan daha fazlası var.

Örneğin, içinde CoundownLatch, the countdown value could not be reset, that can happen in the case of CyclicBarrier.

İkisi arasında başka bir fark var mı?
Nelerdir use casesbirisi geri sayım değerini sıfırlamak istiyor nerede?


12
Sürgüler olayları beklemek içindir; engeller diğer konuları beklemek içindir. - Uygulamada Java Eşzamanlılığı, B.Goetz ve ark.
user2418306

Yanıtlar:


137

Önemli bir fark, CyclicBarrier'ın ortak bariyer koşulu karşılandığında çalıştırılan (isteğe bağlı) Çalıştırılabilir bir görev almasıdır.

Ayrıca bariyerde bekleyen müşteri sayısını ve bariyeri tetiklemek için gereken sayıyı almanızı sağlar. Tetiklendikten sonra bariyer sıfırlanır ve tekrar kullanılabilir.

Basit kullanım durumları için - hizmetlerin başlatılması vb ... bir CountdownLatch iyidir. Bir CyclicBarrier daha karmaşık koordinasyon görevleri için kullanışlıdır. Böyle bir şeye örnek, hesaplamada birden fazla alt görevin yer aldığı paralel hesaplama olabilir ( MapReduce) .


6
"Ayrıca bariyerde bekleyen müşteri sayısını ve bariyeri tetiklemek için gereken sayıyı almanızı sağlar. Tetiklendiğinde bariyer sıfırlanır ve tekrar kullanılabilir." Bu noktayı gerçekten çok seviyorum. Okuduğum birkaç makale, reset () yöntemini çağırdığınız için CyclicBarrier'in döngüsel olduğunu önerdi. Bu doğrudur, ancak sık sık bahsetmedikleri şey, bariyerin tetiklendiği anda otomatik olarak sıfırlanmasıdır. Bunu göstermek için bazı örnek kod göndereceğim.
Kevin Lee

@Kevin Lee "Bariyer tetiklendiğinde otomatik olarak sıfırlanır." bu nedenle kodda reset () çağrılmasına gerek yoktur.
süpernova

134

Başka bir fark daha var.

A kullanırken CyclicBarrier, engeli tetikleyen bekleyen iş parçacıklarının sayısını belirttiğiniz varsayılır. 5 belirtirseniz, aramak için en az 5 iş parçacığınızın olması gerekir await().

A kullanırken CountDownLatch, countDown()bekleyen tüm iş parçacıklarının serbest bırakılmasına neden olacak aramaların sayısını belirtirsiniz . Bu CountDownLatch, yalnızca tek bir iş parçacığı olan a'yı kullanabileceğiniz anlamına gelir .

“Bunu neden yaptın?” Diyebilirsiniz. Geri arama yapan başka biri tarafından kodlanmış gizemli bir API kullandığınızı düşünün. İleti dizilerinizden birinin belirli bir geri arama birkaç kez çağrılıncaya kadar beklemesini istiyorsunuz. Geri aramanın hangi konulara çağrılacağına dair hiçbir fikriniz yok. Bu durumda, a CountDownLatchmükemmel, oysa bunu kullanarak uygulamak için herhangi bir yol düşünemiyorum CyclicBarrier(aslında, yapabilirim, ama zaman aşımları ... yuck!).

Keşke bu CountDownLatchsıfırlanabilir!


10
Bence teorik farklılıkları daha iyi gösteren cevap bu. Mandalların sadece bir yöntemi birden çok kez çağırarak kırılabileceği gerçeği, engeller beklemek için kesin bir iplik miktarına ihtiyaç duyar ().
flagg19

43
Doğru - bu büyük fark: CountDownLatch -> NumberOfCalls, CyclicBarrier -> NumberOfThreads
Ivan Voroshilin

1
CountDownLatchSıfırlanabilir olmak için harika olacağını kabul ediyorum - kaba bir bekleme bildirimi uygulamak için kullandığım bir geçici çözüm CountDownLatch, korumalı kod bloğu girildiğinde (mandal sıfıra ulaştığında) hemen yeni bir şey. Bu, her koşulda / kapsamda geçerli değildir, ancak goldilocks durumlarında bir seçenek olduğunu belirtmeye değer olduğunu düşündüm.
Ephemera

2
Bu konuda en iyi cevaplardan biri. Java Concurrency in Practice- aynı şeyi söylüyor: Latches are for waiting for events; barriers are for waiting for other threads.. Bu ikisi arasındaki farkı anlamak için birincil ve önemli bir nokta.
Rahul Dev Mishra

Java 8 doc "N iş parçacığı bir eylemi tamamlayana veya N eylemi tamamlandığında bir iş parçacığını bekletmek için N olarak başlatılmış bir CountDownLatch kullanılabilir" diyor. bana öyle geliyor: CountDownLatch -> NumberOfCalls Veya CountDownLatch -> NumberOfThreads
nir

41

Henüz kimsenin bahsetmediği bir nokta, CyclicBarrierbir iş parçacığında bir sorun varsa (zaman aşımı, kesintiye uğradı ...), ulaşan diğerlerinin await()bir istisna bulmasıdır. Bkz. Javadoc:

CyclicBarrier, başarısız senkronizasyon denemeleri için tümü veya hiçbiri kırılma modeli kullanır: Bir iş parçacığı, kesinti, hata veya zaman aşımı nedeniyle bir engel noktasını erken terk ederse, bu engel noktasında bekleyen diğer tüm evreler de BrokenBarrierException (veya InterruptedException aracılığıyla anormal olarak bırakılır) onlar da aynı anda yarıda kesildiyse).


22

JavaDoc'un farklılıkları açıkça açıkladığını düşünüyorum. Çoğu kişi CountDownLatch'in sıfırlanamayacağını bilir, ancak CyclicBarrier bunu yapabilir. Ancak bu tek fark değildir veya CyclicBarrier, ResetbleCountDownLatch olarak yeniden adlandırılabilir. Farklılıkları JavaDoc'ta açıklanan hedefleri açısından anlatmalıyız

CountDownLatch: Bir veya daha fazla iş parçacığının, diğer iş parçacıklarında gerçekleştirilen bir dizi işlem tamamlanana kadar beklemesini sağlayan bir eşitleme yardımcısı.

CyclicBarrier: Bir dizi iş parçacığının birbirlerinin ortak bir bariyer noktasına ulaşmasını beklemesine izin veren bir senkronizasyon yardımcısı.

CountDownLatch içinde, diğer iş parçacıklarının tamamlanmasını bekleyen bir veya daha fazla iş parçacığı vardır . Bu durumda, iki tür iş parçacığı vardır, bir tür bekliyor, başka bir tür bir şey yapıyor, görevlerini bitirdikten sonra bekliyor olabilirler veya sonlandırılabilirler.

CyclicBarrier'de sadece bir tür iş parçacığı vardır, birbirlerini beklerler, eşittirler.


1
"CyclicBarrier'de yalnızca bir tür evre vardır" ... Diğer evreler .await () olarak adlandırılana kadar "bekleme rolleri" nde eşittirler, ancak "yaptıkları işte eşit olmayabilir". Ayrıca, hepsi aynı türden veya farklı türden tamamen farklı iş parçacığı örnekleri (!) Olmalıdır, ancak CountDownLatch içinde aynı iş parçacığı countDown () öğesini çağırabilir ve sonucu etkileyebilir.
Vladimir Nabokov

CountDownLatch'in doğası gereği iki rol gerektirdiğini kabul ediyorum: countDown için bir istemci ve bekliyor için bir istemci. Öte yandan, CyclicBarrier istemcileri bekleme yöntemi ile gayet iyi çalışabilir.
isaolmez

14

Temel fark doğrudan CountdownLatch için Javadocs'ta belgelenmiştir. Yani:

Belirli bir sayı ile bir CountDownLatch başlatılır. Await yöntemleri, countDown () yönteminin çağrılması nedeniyle geçerli sayı sıfıra gelinceye kadar bloke edilir, bundan sonra tüm bekleyen evreler serbest bırakılır ve sonraki tüm çağrılar hemen geri döner. Bu tek seferlik bir olgudur - sayım sıfırlanamaz. Sayıyı sıfırlayan bir sürüme ihtiyacınız varsa, bir CyclicBarrier kullanmayı düşünün.

kaynak 1.6 Javadoc


4
Farkları sıfırlanıp sıfırlanamıyorsa, CyclicBarrier daha iyi ResetableCountDownLatch olarak adlandırılabilir, bu farktan dolayı daha anlamlıdır.
James.Xu

12

Bir defalık senkronizasyon için bir CountDownLatch kullanılır. CountDownLatch kullanırken, herhangi bir iş parçacığının countDown () işlevini istediği kadar çağırmasına izin verilir. Await () olarak adlandırılan konular, engellenmemiş diğer iş parçacıklarının countDown () öğesine yapılan çağrılar nedeniyle sayım sıfıra ulaşıncaya kadar engellenir. CountDownLatch için javadoc devletler:

Await yöntemleri, countDown () yönteminin çağrılması nedeniyle geçerli sayı sıfıra gelinceye kadar bloke edilir, bundan sonra tüm bekleyen evreler serbest bırakılır ve sonraki tüm çağrılar hemen geri döner. ...

Diğer bir tipik kullanım, bir problemi N parçalara bölmek, her parçayı o kısmı çalıştıran ve mandalda geriye doğru sayan ve tüm Runnable'ları bir Executor'a sıralayan bir Runnable ile tanımlamaktır. Tüm alt parçalar tamamlandığında, koordine edici iplik beklemeden geçebilir. (İş parçacıklarının bu şekilde art arda geri sayılması gerektiğinde, bunun yerine bir CyclicBarrier kullanın.)

Aksine, döngüsel bariyer birden fazla senkronizasyon noktası için kullanılır, örneğin bir dizi iş parçacığı bir döngü / aşamalı hesaplama çalıştırıyorsa ve bir sonraki iterasyon / faza başlamadan önce senkronize edilmesi gerekiyorsa. Gereğince CyclicBarrier için javadoc :

Bariyere döngüsel denir, çünkü bekleyen iplikler serbest bırakıldıktan sonra tekrar kullanılabilir.

CountDownLatch'ten farklı olarak, her await () çağrısı bir aşamaya aittir ve o aşamaya ait tüm taraflar await () çağrılıncaya kadar iş parçacığının engellenmesine neden olabilir. CyclicBarrier tarafından desteklenen hiçbir açık countDown () işlemi yoktur.


12

Bu soru zaten yeterince cevaplandı, ancak bazı kod göndererek biraz değer katabileceğimi düşünüyorum.

Döngüsel bariyerin davranışını göstermek için bazı örnek kodlar yaptım. Bariyer devrilir kapanmaz, tekrar kullanılabilmesi için otomatik olarak sıfırlanır (dolayısıyla "döngüsel" olur). Programı çalıştırdığınızda, "Hadi oynayalım" çıktılarının ancak bariyer devrildikten sonra tetiklendiğini gözlemleyin.

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

Sürgüler ve siklik bariyerler üzerinde çalışırken bu metaforları buldum. döngüsel engeller : Bir şirketin bir toplantı odası olduğunu hayal edin. Toplantıya başlamak için belirli sayıda toplantı katılımcısı toplantıya gelmelidir (resmi hale getirmek için). aşağıda normal bir toplantı katılımcının kodu (çalışan)

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

çalışan toplantıya katılır, başkalarının toplantıya başlamasını bekler. ayrıca toplantı iptal edilirse çıkılır :) o zaman BOSS'un dozları başkalarının görünmesini beklemek istemez ve hastasını kaybederse toplantıyı iptal eder.

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

Normal bir günde, çalışanlar toplantıya gelip diğerlerinin görünmesini beklerler ve bazı katılımcılar gelmezse süresiz olarak beklemek zorunda kalırlar! bazı özel toplantılarda patron gelir ve beklemekten hoşlanmaz.

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

Çıktı:

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

Başka bir yabancı iş parçacığının (bir deprem) toplantıyı iptal ettiği başka bir senaryo daha vardır (çağrı sıfırlama yöntemi). bu durumda tüm bekleyen evreler bir istisna ile uyandırılır.

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

çalışan kod komik çıktı ile sonuçlanacaktır:

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

Ayrıca toplantı odasına bir sekreter de ekleyebilirsiniz, eğer bir toplantı yapılırsa her şeyi belgeleyecektir, ancak toplantının bir parçası değildir:

class MeetingSecretary implements Runnable {

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

Mandallar : Öfkeli patron şirket müşterileri için bir sergi düzenlemek istiyorsa, her şeyin hazır olması gerekir (kaynaklar). her işçiye (İş Parçacığı) işini dozlayan bir yapılacaklar listesi sağlıyoruz ve yapılacaklar listesini kontrol ediyoruz (bazı çalışanlar resim yapıyor, diğerleri ses sistemi hazırlıyor ...). yapılacaklar listesindeki tüm öğeler tamamlandığında (kaynaklar sağlanır) müşterilere kapıları açabiliriz.

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

Ve işçiler sergiyi nasıl hazırlıyorlar:

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

7

Özetle , sadece ikisi arasındaki temel fonksiyonel farklılıkları anlamak için:

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

    }
}

ve

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

tabii ki, engellemeyen, zamanlı bekleme, teşhis ve yukarıdaki cevaplarda ayrıntılı olarak açıklanan her şey hariç.

Bununla birlikte, yukarıdaki sınıflar, sağlanan işlevsellik içinde tamamen işlevsel ve muhabir adlarıyla eşdeğerdir.

Farklı bir kayda göre, CountDownLatch'nin iç sınıf alt sınıfları AQSederken, CyclicBarrierkullanımları ReentrantLock(- performans verimliliği kaybı olmadan benim şüphe onu tersi olabilir ya da her ikisi AQS'yi veya her ikisi kullanımını Kilit kullanabilirsiniz)


5

Açık olan bir fark, bir C döngüsünün serbest bırakılması için N'nin CyclicBarrier öğesinde yalnızca N iş parçacıkları beklenebilir. Ancak, N'nin CountDownLatch öğesinde sınırsız sayıda iş parçacığı beklenebilir. Geri sayım azalması, bir iş parçacığı N kez veya N iş parçacığı her biri veya kombinasyonlar için yapılabilir.


4

CyclicBarrier durumunda, TÜM alt iş parçacıkları barrier.await () öğesini çağırmaya başlar başlamaz Runnable, Bariyer'de yürütülür. Her bir çocuk ipliğinde bekleyen bariyer, bitirmek için farklı zaman alır ve hepsi aynı anda biter.


4

Gelen CountDownLatch , diğer iş için ana konuları bekler bunların yerine tamamlayın. In CyclicBarrier , çalışan iş parçacığı bunların yerine getirilmesini tamamlamak için birbirlerine bekleyin.

Sayım sıfıra ulaştığında ve mandal açıldığında aynı CountDownLatch örneğini yeniden kullanamazsınız , Öte yandan Bariyer sıfırlandıktan sonra CyclicBarrier yeniden kullanılabilir, Bariyer bozulduğunda.


Ana iş parçacığı olması gerekmez. CountDownLatch oluşturan ve diğer ana olmayan iş parçacıkları ile paylaşan herhangi bir iş parçacığı olabilir.
Aniket Thakur

1

CountDownLatch her şeyin geri sayımıdır; CyclicBarrier yalnızca iş parçacığı için geri sayımdır

5 işçi ipliği ve bir nakliyeci ipliği olduğunu varsayalım ve işçiler 100 ürün ürettiğinde, nakliyeci bunları gönderecektir.

CountDownLatch için sayaç işçiler veya öğeler üzerinde olabilir

CyclicBarrier için sayaç sadece işçiler üzerinde

Bir işçi öğeler üzerinde CountDownLatch ile sonsuz uykuya düşerse, Gönderici gönderebilir; Ancak, CyclicBarrier ile Gönderici asla çağrılamaz


0

@Kevin Lee ve @Jon CyclicBarrier'ı İsteğe Bağlı Runnable ile denedim. Görünüşe göre başlangıçta ve CyclicBarrier uçlu olduktan sonra çalışıyor. İşte kod ve çıktı

statik Döngüsel Bariyer bariyeri;

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

Çıktı

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.
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.