Java concurrency: Countdown latch vs Cyclic barrier

160

Saya membaca melalui java.util.concurrent API , dan menemukan itu

  • CountDownLatch: Bantuan sinkronisasi yang memungkinkan satu atau beberapa utas menunggu hingga serangkaian operasi dilakukan pada utas lainnya selesai.
  • CyclicBarrier: Alat bantu sinkronisasi yang memungkinkan satu set utas semua menunggu satu sama lain untuk mencapai titik penghalang umum.

Bagi saya keduanya tampak sama, tetapi saya yakin ada lebih dari itu.

Misalnya, dalam CoundownLatch, the countdown value could not be reset, that can happen in the case of CyclicBarrier.

Apakah ada perbedaan lain di antara keduanya?
Di use casesmana seseorang ingin mereset nilai hitung mundur?

pelamun
sumber
12
Kait untuk menunggu acara; hambatan untuk menunggu utas lainnya. - Java Concurrency in Practice, B. Goetz et al.
user2418306

Jawaban:

137

Satu perbedaan utama adalah bahwa CyclicBarrier mengambil tugas Runnable (opsional) yang dijalankan setelah kondisi penghalang umum terpenuhi.

Ini juga memungkinkan Anda untuk mendapatkan jumlah klien yang menunggu di penghalang dan jumlah yang dibutuhkan untuk memicu penghalang. Setelah dipicu penghalang diatur ulang dan dapat digunakan lagi.

Untuk kasus penggunaan sederhana - layanan mulai dll ... CountdownLatch baik-baik saja. CyclicBarrier berguna untuk tugas koordinasi yang lebih kompleks. Contoh dari hal seperti itu adalah komputasi paralel - di mana banyak subtugas terlibat dalam perhitungan - semacam MapReduce seperti .

Jon
sumber
6
"Ini juga memungkinkan kamu untuk mendapatkan jumlah klien yang menunggu di penghalang dan jumlah yang diperlukan untuk memicu penghalang. Setelah dipicu penghalang diatur ulang dan dapat digunakan lagi." Saya sangat suka poin ini. Beberapa artikel yang saya baca menyarankan bahwa CyclicBarrier adalah cyclic karena Anda memanggil metode reset (). Itu benar, tetapi apa yang tidak sering mereka sebutkan adalah bahwa penghalang diatur ulang secara otomatis segera setelah dipicu. Saya akan memposting beberapa contoh kode untuk menggambarkan ini.
Kevin Lee
@ Kevin Lee Terima kasih untuk "penghalang diatur ulang secara otomatis segera setelah dipicu." jadi tidak perlu memanggil reset () dalam kode.
supernova
134

Ada perbedaan lain.

Saat menggunakan a CyclicBarrier, asumsinya adalah Anda menentukan jumlah utas yang memicu penghalang. Jika Anda menentukan 5, Anda harus memiliki setidaknya 5 utas untuk dihubungi await().

Saat menggunakan a CountDownLatch, Anda menentukan jumlah panggilan countDown()yang akan menghasilkan semua utas tunggu yang dirilis. Ini berarti bahwa Anda dapat menggunakan CountDownLatchdengan hanya satu utas.

"Mengapa kamu melakukan itu?", Kamu mungkin berkata. Bayangkan Anda menggunakan API misterius yang dikodekan oleh orang lain yang melakukan panggilan balik. Anda ingin salah satu utas Anda menunggu hingga panggilan balik tertentu telah dipanggil beberapa kali. Anda tidak tahu utas mana yang akan dipanggil kembali. Dalam hal ini, a CountDownLatchsempurna, sedangkan saya tidak bisa memikirkan cara untuk mengimplementasikan ini menggunakan CyclicBarrier(sebenarnya, saya bisa, tetapi ini melibatkan waktu tunggu ... yuck!).

Saya hanya berharap itu CountDownLatchbisa diatur ulang!

Kim
sumber
10
Saya pikir ini adalah jawaban yang lebih baik menunjukkan perbedaan teoretis. Fakta bahwa kait dapat dipecah hanya dengan memanggil beberapa waktu metode sementara penghalang membutuhkan jumlah thread yang tepat untuk menunggu ().
flagg19
43
Kanan - itulah perbedaan utama: CountDownLatch -> NumberOfCalls, CyclicBarrier -> NumberOfThreads
Ivan Voroshilin
1
Saya setuju itu akan bagus untuk CountDownLatchdapat direset - solusi yang saya gunakan untuk menerapkan pemberitahuan tunggu kasar hanya untuk CountDownLatchsegera memulai segera ketika blok kode yang dilindungi dimasukkan (ketika kait mencapai nol). Ini tidak berlaku dalam semua keadaan / cakupan tentu saja, tetapi saya pikir perlu dicatat bahwa ini adalah opsi dalam situasi goldilocks.
Ephemera
2
Salah satu jawaban terbaik tentang topik ini. Java Concurrency in Practice- mengatakan hal yang sama: Latches are for waiting for events; barriers are for waiting for other threads.. Poin utama dan esensial untuk memahami perbedaan antara keduanya.
Rahul Dev Mishra
Java 8 doc mengatakan "A CountDownLatch yang diinisialisasi ke N dapat digunakan untuk membuat satu utas menunggu sampai utas menyelesaikan beberapa tindakan, atau beberapa tindakan telah selesai sebanyak N kali." menurut saya: CountDownLatch -> NumberOfCalls Atau CountDownLatch -> NumberOfThreads
nir
41

Satu hal yang belum ada yang disebutkan adalah bahwa, CyclicBarrierjika, sebuah utas memiliki masalah (batas waktu, terputus ...), semua yang lain yang telah mencapai await()dapatkan pengecualian. Lihat Javadoc:

CyclicBarrier menggunakan model kerusakan semua atau tidak sama sekali untuk upaya sinkronisasi yang gagal: Jika sebuah thread meninggalkan titik penghalang sebelum waktunya karena gangguan, kegagalan, atau batas waktu, semua utas lain yang menunggu di titik penghalang itu juga akan keluar secara tidak normal melalui BrokenBarrierException (atau InterruptedException) jika mereka juga terganggu pada waktu yang bersamaan).

Chirlo
sumber
22

Saya pikir JavaDoc telah menjelaskan perbedaan secara eksplisit. Kebanyakan orang tahu bahwa CountDownLatch tidak dapat diatur ulang, namun, CyclicBarrier bisa. Tapi ini bukan satu-satunya perbedaan, atau CyclicBarrier dapat diubah namanya menjadi ResetbleCountDownLatch. Kita harus memberi tahu perbedaan dari perspektif tujuan mereka, yang dijelaskan dalam JavaDoc

CountDownLatch: Alat bantu sinkronisasi yang memungkinkan satu atau lebih utas menunggu hingga serangkaian operasi dilakukan pada utas lainnya selesai.

CyclicBarrier: Alat bantu sinkronisasi yang memungkinkan satu set utas untuk semua menunggu satu sama lain untuk mencapai titik penghalang bersama.

Di countDownLatch, ada satu atau lebih utas, yang sedang menunggu satu set utas lainnya selesai. Dalam situasi ini, ada dua jenis utas, satu jenis menunggu, jenis lain sedang melakukan sesuatu, setelah menyelesaikan tugas mereka, mereka bisa menunggu atau diakhiri.

Di CyclicBarrier, hanya ada satu jenis utas, mereka saling menunggu, mereka sama.

Justin Civi
sumber
1
"Di CyclicBarrier, hanya ada satu jenis utas" ... Mereka sama dalam "peran menunggu" mereka sampai utas lainnya memanggil .await (), tetapi mereka mungkin "tidak sama dengan apa yang mereka lakukan". Semua itu harus benar-benar berbeda contoh instance (!) Dari jenis yang sama atau dari jenis yang berbeda, sementara di CountDownLatch utas yang sama dapat memanggil countDown () dan mempengaruhi hasilnya.
Vladimir Nabokov
Saya setuju bahwa CountDownLatch secara inheren memerlukan dua peran: satu klien untuk countDown dan satu klien untuk menunggu. Di sisi lain, klien CyclicBarrier dapat beroperasi dengan baik dengan metode menunggu.
isaolmez
14

Perbedaan utama didokumentasikan tepat di Javadocs untuk CountdownLatch. Yaitu:

CountDownLatch diinisialisasi dengan jumlah yang diberikan. Metode menunggu menunggu hingga hitungan saat ini mencapai nol karena doa dari metode countDown (), setelah semua utas menunggu dilepaskan dan setiap invokasi selanjutnya menunggu segera kembali. Ini adalah fenomena sekali pakai - penghitungan tidak dapat diatur ulang. Jika Anda membutuhkan versi yang menyetel ulang hitungan, pertimbangkan untuk menggunakan CyclicBarrier.

sumber 1.6 Javadoc

JohnnyO
sumber
4
Jika perbedaannya hanya dapat disetel ulang atau tidak, CyclicBarrier mungkin lebih baik dinamai ResetableCountDownLatch, yang lebih bermakna karena perbedaannya.
James.Xu
12

CountDownLatch digunakan untuk sinkronisasi satu kali. Saat menggunakan CountDownLatch, utas apa pun diizinkan untuk memanggil countDown () sebanyak yang mereka suka. Utas yang disebut await () diblokir hingga hitungannya mencapai nol karena panggilan ke countDown () oleh utas lain yang tidak dicekal. The javadoc untuk status CountDownLatch :

Metode menunggu menunggu hingga hitungan saat ini mencapai nol karena doa dari metode countDown (), setelah semua utas menunggu dilepaskan dan setiap invokasi selanjutnya menunggu segera kembali. ...

Penggunaan khas lainnya adalah untuk membagi masalah menjadi N bagian, menggambarkan setiap bagian dengan Runnable yang mengeksekusi bagian itu dan menghitung mundur pada kait, dan mengantri semua Runnables ke Eksekutor. Ketika semua sub-bagian selesai, utas koordinasi akan dapat melewati menunggu. (Saat utas berulang kali harus menghitung mundur dengan cara ini, alih-alih gunakan CyclicBarrier.)

Sebaliknya, penghalang siklik digunakan untuk beberapa titik sinkronisasi, misalnya jika serangkaian thread menjalankan perhitungan loop / bertahap dan perlu disinkronkan sebelum memulai iterasi / fase berikutnya. Sesuai javadoc untuk CyclicBarrier :

Penghalang disebut siklik karena dapat digunakan kembali setelah utas menunggu dilepaskan.

Berbeda dengan CountDownLatch, setiap panggilan untuk menunggu () milik beberapa fase dan dapat menyebabkan utas untuk memblokir sampai semua pihak yang termasuk dalam fase itu telah meminta menunggu (). Tidak ada operasi countDown () eksplisit yang didukung oleh CyclicBarrier.

Syams
sumber
12

Pertanyaan ini sudah cukup dijawab, tetapi saya rasa saya bisa memberi nilai tambah sedikit dengan memposting beberapa kode.

Untuk menggambarkan perilaku penghalang siklik, saya telah membuat beberapa kode sampel. Segera setelah penghalang berujung, ia secara otomatis diatur ulang sehingga dapat digunakan kembali (oleh karena itu disebut "siklik"). Saat Anda menjalankan program, amati bahwa cetakan "Ayo main" dipicu hanya setelah penghalang dimiringkan.

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
sumber
8

Ketika saya mempelajari tentang kait dan penghalang siklik, saya menemukan metafora ini. cyclicbarriers : Bayangkan sebuah perusahaan memiliki ruang pertemuan. Untuk memulai pertemuan, sejumlah peserta rapat harus datang ke pertemuan (untuk menjadikannya resmi). berikut ini adalah kode peserta rapat biasa (seorang karyawan)

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

karyawan bergabung dengan rapat, menunggu orang lain datang untuk memulai rapat. juga dia akan keluar jika pertemuan dibatalkan :) maka kita memiliki THE BOSS bagaimana dosis tidak suka menunggu orang lain muncul dan jika dia kehilangan pasiennya, dia membatalkan pertemuan.

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

Pada hari normal, karyawan datang ke rapat menunggu yang lain muncul dan jika beberapa peserta tidak datang, mereka harus menunggu tanpa batas waktu! dalam beberapa pertemuan khusus, bos datang dan dia tidak suka menunggu. (5 orang perlu memulai rapat tetapi hanya bos yang datang dan juga karyawan yang antusias) sehingga dia membatalkan rapat (dengan marah)

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

Keluaran:

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

Ada skenario lain di mana utas luar orang lain (gempa bumi) membatalkan pertemuan (metode reset panggilan). dalam hal ini semua utas menunggu dibangunkan oleh pengecualian.

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

menjalankan kode akan menghasilkan keluaran lucu:

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

Anda juga dapat menambahkan sekretaris ke ruang rapat, jika rapat diadakan, dia akan mendokumentasikan semuanya tetapi dia bukan bagian dari rapat:

class MeetingSecretary implements Runnable {

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

Kait : jika bos yang marah ingin mengadakan pameran untuk pelanggan perusahaan, semua hal harus siap (sumber daya). kami memberikan daftar tugas yang harus dilakukan setiap pekerja (Utas) dosis pekerjaannya dan kami memeriksa daftar yang harus dilakukan (beberapa pekerja melukis, yang lain menyiapkan sistem suara ...). ketika semua item dalam daftar yang harus dilakukan sudah lengkap (sumber daya disediakan) kami dapat membuka pintu bagi pelanggan.

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

Dan para pekerja bagaimana mempersiapkan pameran:

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();
Mr.Q
sumber
6

Singkatnya , hanya untuk memahami perbedaan fungsional utama antara keduanya:

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

    }
}

dan

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

kecuali, tentu saja, fitur-fitur seperti non-blocking, menunggu waktunya, diagnostik dan segala sesuatu yang telah secara rinci dijelaskan dalam jawaban di atas.

Kelas-kelas di atas, bagaimanapun, berfungsi penuh dan setara, dalam fungsionalitas yang disediakan, sesuai dengan nama koresponden mereka.

Pada catatan yang berbeda, CountDownLatchadalah kelas dalam subkelas AQS, saat CyclicBarriermenggunakan ReentrantLock(kecurigaan saya adalah bisa sebaliknya atau keduanya bisa menggunakan AQS atau keduanya menggunakan Kunci - tanpa kehilangan efisiensi kinerja)

igor.zh
sumber
5

Satu perbedaan yang jelas adalah, hanya N thread yang dapat menunggu pada CyclicBarrier of N untuk dirilis dalam satu siklus. Tetapi jumlah utas yang tidak terbatas dapat menunggu pada CountDownLatch dari N. Penurunan jumlah mundur dapat dilakukan dengan satu utas N kali atau utas N satu kali masing-masing atau kombinasi.

Pramma
sumber
4

Dalam kasus CyclicBarrier, segera setelah SEMUA utas anak mulai memanggil barrier.await (), Runnable dieksekusi di Barrier. Penghalang. Tunggu di setiap utas anak akan membutuhkan waktu yang berbeda untuk menyelesaikannya, dan semuanya selesai pada waktu yang sama.

Brandon
sumber
4

Di CountDownLatch , utas utama menunggu utas lainnya untuk menyelesaikan eksekusi. Di CyclicBarrier , utas pekerja menunggu satu sama lain untuk menyelesaikan eksekusi mereka.

Anda tidak dapat menggunakan kembali contoh CountDownLatch yang sama setelah hitungan mencapai ke nol dan kait terbuka, di sisi lain CyclicBarrier dapat digunakan kembali dengan mengatur ulang Barrier, Setelah penghalang rusak.

V Jo
sumber
Itu tidak perlu menjadi utas utama. Ini bisa berupa utas apa pun yang membuat CountDownLatch dan membagikannya dengan utas non-utama lainnya.
Aniket Thakur
1

CountDownLatch adalah hitungan mundur dari apa pun; CyclicBarrier adalah hitungan mundur untuk utas saja

anggap ada 5 utas pekerja dan satu utas pengirim, dan ketika pekerja menghasilkan 100 barang, pengirim akan mengirimkannya.

Untuk CountDownLatch, penghitung bisa berada di pekerja atau item

Untuk CyclicBarrier, penghitung hanya bisa pada pekerja

Jika seorang pekerja jatuh tertidur tanpa batas, dengan CountDownLatch pada item, Pengirim dapat mengirim; Namun, dengan CyclicBarrier, Pengirim tidak pernah bisa dipanggil

yk42b
sumber
0

@Kevin Lee dan @Jon saya mencoba CyclicBarrier dengan Runnable Opsional. Sepertinya itu berjalan di awal dan setelah CyclicBarrier berujung. Ini kode dan keluarannya

penghalang CyclicBarrier statis;

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

Keluaran

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
sumber