Bagaimana CountDownLatch digunakan dalam Java Multithreading?

184

Dapatkah seseorang membantu saya untuk memahami apa itu Java CountDownLatchdan kapan menggunakannya?

Saya tidak punya ide yang jelas tentang cara kerja program ini. Seperti yang saya pahami ketiga utas mulai sekaligus dan setiap utas akan memanggil CountDownLatch setelah 3000ms. Jadi hitung mundur akan berkurang satu per satu. Setelah kait menjadi nol program mencetak "Selesai". Mungkin cara saya memahami itu salah.

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

class Processor implements Runnable {
    private CountDownLatch latch;

    public Processor(CountDownLatch latch) {
        this.latch = latch;
    }

    public void run() {
        System.out.println("Started.");

        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        latch.countDown();
    }
}

// ------------------------------------------------ -----

public class App {

    public static void main(String[] args) {

        CountDownLatch latch = new CountDownLatch(3); // coundown from 3 to 0

        ExecutorService executor = Executors.newFixedThreadPool(3); // 3 Threads in pool

        for(int i=0; i < 3; i++) {
            executor.submit(new Processor(latch)); // ref to latch. each time call new Processes latch will count down by 1
        }

        try {
            latch.await();  // wait until latch counted down to 0
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("Completed.");
    }

}
amal
sumber
9
Saya baru saja menggunakan kode sampel pertanyaan Anda untuk kumpulan layanan paralel Android dan itu bekerja seperti pesona. Terima kasih banyak!
Roisgoen
Dapatkan di sini dari video ini dari 2012, yang menunjukkan kemiripan yang luar biasa dengan contoh yang ditunjukkan di sini. Bagi siapa pun yang tertarik, ini adalah bagian dari seri tutorial multi-threading Java dari seorang pria bernama John. Saya suka John. Sangat dianjurkan.
Elia Grady

Jawaban:

194

Ya, Anda mengerti dengan benar. CountDownLatchbekerja pada prinsip kait, utas utama akan menunggu sampai gerbang terbuka. Satu utas menunggu n utas, ditentukan saat membuat CountDownLatch.

Setiap utas, biasanya utas utama aplikasi, yang CountDownLatch.await()akan menunggu hingga hitungan mencapai nol atau terputus oleh utas lainnya. Semua utas lainnya diharuskan untuk menghitung mundur dengan menelepon CountDownLatch.countDown()setelah selesai atau siap.

Segera setelah hitungan mencapai nol, utas menunggu berlanjut. Salah satu kelemahan / kelebihan CountDownLatchadalah tidak dapat digunakan kembali: sekali hitungan mencapai nol Anda tidak dapat menggunakan CountDownLatchlagi.

Edit:

Gunakan CountDownLatchketika satu utas (seperti utas utama) mengharuskan untuk menunggu satu atau lebih utas selesai, sebelum dapat melanjutkan pemrosesan.

Contoh klasik penggunaan CountDownLatchdi Jawa adalah aplikasi Java inti sisi server yang menggunakan arsitektur layanan, di mana beberapa layanan disediakan oleh banyak utas dan aplikasi tidak dapat memulai pemrosesan hingga semua layanan mulai berhasil.

Pertanyaan PS OP memiliki contoh yang cukup jelas sehingga saya tidak memasukkan satu pun.

NikolaB
sumber
1
Terima kasih atas balasannya Bisakah Anda memberi saya contoh di mana untuk menerapkan kait CountDown?
amal
11
tutorial tentang cara menggunakan CountDownLatch ada di
thiagoh
1
@NikolaB Tetapi dalam contoh yang diberikan ini kita dapat mencapai hasil yang sama dengan menggunakan metode bergabung bukan?
Vikas Verma
3
Saya akan menganggap keuntungan yang tidak dapat digunakan kembali: Anda yakin tidak ada yang bisa mengatur ulang, atau menambah jumlah.
ataulm
3
Penjelasan yang bagus. Tapi saya akan sedikit tidak setuju tentang hal itu One thread waits for n number of threads specified while creating CountDownLatch in Java. Jika Anda membutuhkan mekanisme seperti itu, maka lebih bijaksana untuk menggunakannya CyclicBarrier. Perbedaan konseptual mendasar antara kedua, seperti yang diberikan dalam Java concurrency in Practiceadalah: Latches are for waiting for events; barriers are for waiting for other threads. cyclicBarrier.await()masuk ke keadaan memblokir.
Rahul Dev Mishra
43

CountDownLatchdi Java adalah jenis sinkronisasi yang memungkinkan seseorang Thread untuk menunggu satu atau lebih Threadsebelum mulai diproses.

CountDownLatchbekerja pada prinsip kait, utas akan menunggu sampai gerbang terbuka. Satu utas menunggu njumlah utas yang ditentukan saat membuat CountDownLatch.

misalnya final CountDownLatch latch = new CountDownLatch(3);

Di sini kita mengatur penghitung ke 3.

Utas apa pun, biasanya utas utama aplikasi, yang CountDownLatch.await()akan menunggu hingga hitungan mencapai nol atau terganggu oleh yang lain Thread. Semua utas lainnya diharuskan untuk melakukan hitung mundur dengan menelepon CountDownLatch.countDown()setelah selesai atau siap untuk pekerjaan itu. segera setelah hitungan mencapai nol, Threadmenunggu mulai berjalan.

Di sini perhitungannya dikurangi dengan CountDownLatch.countDown()metode.

Metode Threadyang memanggil await()metode akan menunggu sampai hitungan awal mencapai nol.

Untuk menghitung nol utas lainnya perlu memanggil countDown()metode. Setelah hitungan menjadi nol utas yang dipanggil await()metode akan dilanjutkan (mulai eksekusi).

Kerugiannya CountDownLatchadalah itu tidak dapat digunakan kembali: sekali hitungan menjadi nol maka tidak lagi dapat digunakan.

Vishal Akkalkote
sumber
Apakah kita menggunakan new CountDownLatch(3)karena kita memiliki 3 utas dari yang newFixedThreadPool ditentukan?
Chaklader Asfak Arefe
tidakkah "sebelum mulai memproses" menjadi "sebelum melanjutkan pemrosesan"?
Maria Ines Parnisari
@Arefe Ya, ini adalah jumlah utas yang melewati blok kode Anda
Vishal Akkalkote
23

NikolaB menjelaskannya dengan sangat baik, Namun contoh akan sangat membantu untuk dipahami, Jadi di sini adalah salah satu contoh sederhana ...

 import java.util.concurrent.*;


  public class CountDownLatchExample {

  public static class ProcessThread implements Runnable {

    CountDownLatch latch;
    long workDuration;
    String name;

    public ProcessThread(String name, CountDownLatch latch, long duration){
        this.name= name;
        this.latch = latch;
        this.workDuration = duration;
    }


    public void run() {
        try {
            System.out.println(name +" Processing Something for "+ workDuration/1000 + " Seconds");
            Thread.sleep(workDuration);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(name+ "completed its works");
        //when task finished.. count down the latch count...

        // basically this is same as calling lock object notify(), and object here is latch
        latch.countDown();
    }
}


public static void main(String[] args) {
    // Parent thread creating a latch object
    CountDownLatch latch = new CountDownLatch(3);

    new Thread(new ProcessThread("Worker1",latch, 2000)).start(); // time in millis.. 2 secs
    new Thread(new ProcessThread("Worker2",latch, 6000)).start();//6 secs
    new Thread(new ProcessThread("Worker3",latch, 4000)).start();//4 secs


    System.out.println("waiting for Children processes to complete....");
    try {
        //current thread will get notified if all chidren's are done 
        // and thread will resume from wait() mode.
        latch.await();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }

    System.out.println("All Process Completed....");

    System.out.println("Parent Thread Resuming work....");



     }
  }
vikashait
sumber
22

Ini digunakan ketika kita ingin menunggu lebih dari satu utas untuk menyelesaikan tugasnya. Ini mirip dengan bergabung dalam utas.

Di mana kita bisa menggunakan CountDownLatch

Pertimbangkan skenario di mana kami memiliki persyaratan di mana kami memiliki tiga utas "A", "B" dan "C" dan kami ingin memulai utas "C" hanya jika utas "A" dan "B" menyelesaikan atau menyelesaikan sebagian tugas mereka.

Ini dapat diterapkan pada skenario IT dunia nyata

Pertimbangkan skenario di mana manajer membagi modul antara tim pengembangan (A dan B) dan dia ingin menugaskannya ke tim QA untuk pengujian hanya ketika kedua tim menyelesaikan tugas mereka.

public class Manager {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(2);
        MyDevTeam teamDevA = new MyDevTeam(countDownLatch, "devA");
        MyDevTeam teamDevB = new MyDevTeam(countDownLatch, "devB");
        teamDevA.start();
        teamDevB.start();
        countDownLatch.await();
        MyQATeam qa = new MyQATeam();
        qa.start();
    }   
}

class MyDevTeam extends Thread {   
    CountDownLatch countDownLatch;
    public MyDevTeam (CountDownLatch countDownLatch, String name) {
        super(name);
        this.countDownLatch = countDownLatch;       
    }   
    @Override
    public void run() {
        System.out.println("Task assigned to development team " + Thread.currentThread().getName());
        try {
                Thread.sleep(2000);
        } catch (InterruptedException ex) {
                ex.printStackTrace();
        }
    System.out.println("Task finished by development team Thread.currentThread().getName());
            this.countDownLatch.countDown();
    }
}

class MyQATeam extends Thread {   
    @Override
    public void run() {
        System.out.println("Task assigned to QA team");
        try {
                Thread.sleep(2000);
        } catch (InterruptedException ex) {
            ex.printStackTrace();
        }
        System.out.println("Task finished by QA team");
    }
}

Output dari kode di atas adalah:

Tugas yang ditugaskan untuk pengembangan tim devB

Tugas yang diberikan kepada pengembang tim pengembang

Tugas diselesaikan oleh tim pengembang devB

Tugas diselesaikan oleh pengembang tim pengembang

Tugas yang diberikan kepada tim QA

Tugas diselesaikan oleh tim QA

Di sini menunggu () metode menunggu flag countdownlatch menjadi 0, dan countDown () metode menurunkan flag countdownlatch oleh 1.

Batasan BERGABUNG: Contoh di atas juga dapat dicapai dengan BERGABUNG, tetapi BERGABUNG tidak dapat digunakan dalam dua skenario:

  1. Saat kami menggunakan ExecutorService alih-alih kelas Thread untuk membuat utas.
  2. Ubah contoh di atas di mana Manajer ingin menyerahkan kode ke tim QA segera setelah Pengembangan menyelesaikan tugas 80% mereka. Ini berarti bahwa CountDownLatch memungkinkan kita untuk memodifikasi implementasi yang dapat digunakan untuk menunggu utas lain untuk eksekusi parsial mereka.
V Jo
sumber
3

CoundDownLatch memungkinkan Anda untuk membuat utas menunggu sampai semua utas lainnya selesai dengan eksekusi mereka.

Kode semu dapat berupa:

// Main thread starts
// Create CountDownLatch for N threads
// Create and start N threads
// Main thread waits on latch
// N threads completes there tasks are returns
// Main thread resume execution
Christophe Roussy
sumber
Anda mungkin ingin memindahkan semua deskripsi Anda dari blok kode
Paul Lo
Komentar terbaik. Saya suka komentar "to the point" ini dan bukannya penjelasan teoretis.
renatoaraujoc
2

Salah satu contoh yang baik kapan harus menggunakan sesuatu seperti ini adalah dengan Java Simple Serial Connector, mengakses port serial. Biasanya Anda akan menulis sesuatu ke port, dan secara asinkron, pada utas lainnya, perangkat akan merespons pada SerialPortEventListener. Biasanya, Anda ingin berhenti setelah menulis ke port untuk menunggu respons. Menangani kunci utas untuk skenario ini secara manual sangat sulit, tetapi menggunakan Countdownlatch mudah. Sebelum Anda berpikir Anda bisa melakukannya dengan cara lain, berhati-hatilah dengan kondisi balapan yang tidak pernah Anda pikirkan !!

Kodesemu:

CountDownLatch latch;
void writeData() { 
   latch = new CountDownLatch(1);
   serialPort.writeBytes(sb.toString().getBytes())
   try {
      latch.await(4, TimeUnit.SECONDS);
    } catch (InterruptedException e) {
   }
}
class SerialPortReader implements SerialPortEventListener {
    public void serialEvent(SerialPortEvent event) {
        if(event.isRXCHAR()){//If data is available
            byte buffer[] = serialPort.readBytes(event.getEventValue());
            latch.countDown();
         }
     }
}

xpusostomos
sumber
2

Jika Anda menambahkan beberapa debug setelah panggilan Anda ke latch.countDown (), ini dapat membantu Anda memahami perilakunya dengan lebih baik.

latch.countDown();
System.out.println("DONE "+this.latch); // Add this debug

Output akan menunjukkan Count dikurangi. 'Hitungan' ini secara efektif adalah jumlah tugas Runnable (objek Prosesor) yang Anda mulai terhadap countDown () yang belum dipanggil dan karenanya diblokir utas utama pada panggilannya ke latch.await ().

DONE java.util.concurrent.CountDownLatch@70e69696[Count = 2]
DONE java.util.concurrent.CountDownLatch@70e69696[Count = 1]
DONE java.util.concurrent.CountDownLatch@70e69696[Count = 0]
natmat
sumber
2

Dari dokumentasi oracle tentang CountDownLatch :

Alat bantu sinkronisasi yang memungkinkan satu atau lebih utas untuk menunggu sampai satu set operasi dilakukan di utas lainnya selesai.

A CountDownLatchdiinisialisasi dengan jumlah yang diberikan. The awaitmetode memblokir sampai mencapai jumlah saat ini nol karena doa dari countDown()metode, setelah semua thread menunggu dilepaskan dan setiap doa selanjutnya pengembalian Tunggulah segera. Ini adalah fenomena sekali pakai - penghitungan tidak dapat diatur ulang.

CountDownLatch adalah alat sinkronisasi serbaguna dan dapat digunakan untuk sejumlah tujuan.

Sebuah CountDownLatchdiinisialisasi dengan hitungan satu servis sebagai sederhana on / off kait, atau gerbang: semua thread yang memanggil menunggu menunggu di pintu gerbang sampai dibuka oleh thread memanggil Countdown ().

Sebuah CountDownLatchdiinisialisasi ke N dapat digunakan untuk membuat satu thread menunggu sampai N benang telah menyelesaikan beberapa tindakan, atau beberapa tindakan telah selesai N kali.

public void await()
           throws InterruptedException

Menyebabkan utas saat ini menunggu sampai kait telah menghitung mundur ke nol, kecuali utasnya terputus.

Jika jumlah saat ini adalah nol maka metode ini segera kembali.

public void countDown()

Mengurangi jumlah kait, melepaskan semua utas tunggu jika jumlah mencapai nol.

Jika jumlah saat ini lebih besar dari nol maka akan dikurangi. Jika jumlah baru adalah nol maka semua utas menunggu diaktifkan kembali untuk tujuan penjadwalan utas.

Penjelasan contoh Anda.

  1. Anda telah menetapkan jumlah sebagai 3 untuk latchvariabel

    CountDownLatch latch = new CountDownLatch(3);
  2. Anda telah membagikan ini latchke utas Pekerja:Processor

  3. Tiga Runnablecontoh Processortelah diserahkan kepadaExecutorService executor
  4. Utas utama ( App) sedang menunggu hitungan menjadi nol dengan pernyataan di bawah ini

     latch.await();  
  5. Processor thread tidur selama 3 detik dan kemudian menurunkan nilai hitung dengan latch.countDown()
  6. Pertama Process virtual akan mengubah jumlah kait menjadi 2 setelah selesai karena latch.countDown().

  7. Mesin Processvirtual kedua akan mengubah jumlah kait sebagai 1 setelah selesai karenalatch.countDown() .

  8. Mesin Processvirtual ketiga akan mengubah jumlah kait sebagai 0 setelah selesai karenalatch.countDown() .

  9. Zero count on latch menyebabkan Appkeluarnya utas utamaawait

  10. Program aplikasi mencetak hasil ini sekarang: Completed

Ravindra babu
sumber
2

Contoh dari Java Doc ini membantu saya memahami konsep dengan jelas:

class Driver { // ...
  void main() throws InterruptedException {
    CountDownLatch startSignal = new CountDownLatch(1);
    CountDownLatch doneSignal = new CountDownLatch(N);

    for (int i = 0; i < N; ++i) // create and start threads
      new Thread(new Worker(startSignal, doneSignal)).start();

    doSomethingElse();            // don't let run yet
    startSignal.countDown();      // let all threads proceed
    doSomethingElse();
    doneSignal.await();           // wait for all to finish
  }
}

class Worker implements Runnable {
  private final CountDownLatch startSignal;
  private final CountDownLatch doneSignal;
  Worker(CountDownLatch startSignal, CountDownLatch doneSignal) {
     this.startSignal = startSignal;
     this.doneSignal = doneSignal;
  }
  public void run() {
     try {
       startSignal.await();
       doWork();
       doneSignal.countDown();
     } catch (InterruptedException ex) {} // return;
  }

  void doWork() { ... }
}

Interpretasi visual:

masukkan deskripsi gambar di sini

Jelas, CountDownLatchmemungkinkan satu utas (di sini Driver) menunggu sampai sekelompok utas yang berjalan (di sini Worker) selesai dengan eksekusi mereka.

Saurav Sahu
sumber
1

Seperti disebutkan dalam JavaDoc ( https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/CountDownLatch.html ), CountDownLatch adalah alat bantu sinkronisasi, diperkenalkan di Java 5. Di sini sinkronisasi tidak berarti membatasi akses ke bagian kritis. Melainkan tindakan pengurutan dari utas yang berbeda. Jenis sinkronisasi yang dicapai melalui CountDownLatch mirip dengan yang dimiliki Join. Asumsikan ada utas "M" yang perlu menunggu utas pekerja lain "T1", "T2", "T3" untuk menyelesaikan tugasnya Sebelum ke Java 1.5, cara ini bisa dilakukan adalah, M menjalankan kode berikut

    T1.join();
    T2.join();
    T3.join();

Kode di atas memastikan bahwa utas M melanjutkan pekerjaannya setelah T1, T2, T3 menyelesaikan pekerjaannya. T1, T2, T3 dapat menyelesaikan pekerjaan mereka dalam urutan apa pun. Hal yang sama dapat dicapai melalui CountDownLatch, di mana T1, T2, T3 dan thread M berbagi objek CountDownLatch yang sama.
"M" meminta: countDownLatch.await();
sedangkan "T1", "T2", "T3" melakukannya countDownLatch.countdown();

Salah satu kelemahan dengan metode join adalah bahwa M harus tahu tentang T1, T2, T3. Jika ada T4 pekerja baru yang ditambahkan kemudian, maka M juga harus menyadarinya. Ini bisa dihindari dengan CountDownLatch. Setelah implementasi, urutan tindakannya adalah [T1, T2, T3] (urutan T1, T2, T3 bisa jadi tetap) -> [M]

SR Chaitanya
sumber
0
package practice;

import java.util.concurrent.CountDownLatch;

public class CountDownLatchExample {

    public static void main(String[] args) throws InterruptedException {
        CountDownLatch c= new CountDownLatch(3);  // need to decrements the count (3) to zero by calling countDown() method so that main thread will wake up after calling await() method 
        Task t = new Task(c);
        Task t1 = new Task(c);
        Task t2 = new Task(c);
        t.start();
        t1.start();
        t2.start();
        c.await(); // when count becomes zero main thread will wake up 
        System.out.println("This will print after count down latch count become zero");
    }
}

class Task extends Thread{
    CountDownLatch c;

    public Task(CountDownLatch c) {
        this.c = c;
    }

    @Override
    public void run() {
        try {
            System.out.println(Thread.currentThread().getName());
            Thread.sleep(1000);
            c.countDown();   // each thread decrement the count by one 
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
sumit
sumber