CountDownLatch, Java Multithreading'de nasıl kullanılır?


184

Birisi Java'nın ne olduğunu anlamama yardımcı olabilir mi? CountDownLatch zaman ve ne zaman kullanılacağını olabilir mi?

Bu programın nasıl çalıştığı hakkında çok net bir fikrim yok. Anladığım gibi, üç iş parçacığı aynı anda başlar ve her iş parçacığı 3000 ms sonra CountDownLatch arayacaktır. Böylece geri sayım tek tek azalacaktır. Mandal sıfırlandıktan sonra program "Tamamlandı" yazdırır. Belki anladığım yol yanlıştır.

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

}


9
Ben sadece bir android paralel servis toplu iş için soru örnek kodunu kullandım ve bir cazibe gibi çalıştı. Çok teşekkür ederim!
Roisgoen

Burada gösterilen örneğe dikkat çekici benzerlik gösteren 2012'den bu videodan buraya geldim. İlgilenen herkes için bu, John adında bir adamdan gelen Java çok iş parçacıklı öğretici dizisinin bir parçasıdır. John'u seviyorum. Şiddetle tavsiye edilir.
Elia Grady

Yanıtlar:


194

Evet, doğru anladın. CountDownLatchmandal prensibi ile çalışır, ana iplik kapı açılana kadar bekleyecektir. İçin bir iplik bekler N oluşturulurken belirtilen iplikleri,CountDownLatch .

CountDownLatch.await()Sayılar sıfıra ulaşana veya başka bir iş parçacığı tarafından kesilene kadar çağrıları bekleyecek olan, genellikle uygulamanın ana iş parçacığı olan herhangi bir iş parçacığı. Diğer tüm evreleri arayarak geri saymak gerekirCountDownLatch.countDown() , tamamlandıktan veya hazır olduktan sonra .

Sayım sıfıra ulaştığında, bekleyen iplik devam eder. Dezavantajlarından / avantajlarından biri CountDownLatchtekrar kullanılamamasıdır: sayım sıfıra ulaştığında kullanamazsınızCountDownLatch .

Düzenle:

kullanım CountDownLatch İşlenmesi devam edebilmesi için (ana iplik gibi) bir iş parçacığı, tam bir veya daha fazla iş parçacığı için beklemek gerektirdiğinde.

CountDownLatchJava'da kullanımın klasik bir örneği, birden çok iş parçacığı tarafından birden çok hizmetin sağlandığı ve uygulamanın tüm hizmetler başarıyla başlatılıncaya kadar işlemeye başlayamadığı, hizmet mimarisini kullanan bir sunucu tarafı çekirdek Java uygulamasıdır.

PS OP'nin sorusunun oldukça basit bir örneği var, bu yüzden bir tane eklemedim.


1
Cevabınız için teşekkür ederim. CountDown mandalının nereye uygulanacağına dair bir örnek verebilir misiniz?
amal

11
CountDownLatch'un nasıl kullanılacağına dair bir eğitici burada howtodoinjava.com/2013/07/18/…
thiagoh

1
@NikolaB Fakat bu örnekte join yöntemini kullanarak aynı sonucu elde edebiliriz değil mi?
Vikas Verma

3
Tekrar kullanılamazlığın bir avantaj olduğunu düşünürüm: Kimsenin sıfırlayamayacağından veya sayıyı artıramayacağından eminsiniz.
ataulm

3
Güzel açıklama. Ama konuya biraz katılmıyorum One thread waits for n number of threads specified while creating CountDownLatch in Java. Böyle bir mekanizmaya ihtiyacınız varsa, kullanmak ihtiyatlıdır CyclicBarrier. Olarak verilmiştir, bu ikisi arasındaki temel kavramsal bir fark Java concurrency in Practiceolduğu: Latches are for waiting for events; barriers are for waiting for other threads. cyclicBarrier.await()engelleme durumuna geçer.
Rahul Dev Mishra

43

CountDownLatchJava'da Thread bir veya daha fazla beklemenizi sağlayan bir tür eşzamanlayıcıdırThread işlemeye başlamadan önce s .

CountDownLatchmandal prensibi ile çalışır, kapı açılana kadar iplik bekleyecektir. Bir iş parçacığı noluştururken belirtilen iş parçacığı sayısını beklerCountDownLatch .

Örneğin final CountDownLatch latch = new CountDownLatch(3);

Burada sayacı 3'e ayarladık.

Herhangi iplik, aramalar uygulamanın genellikle ana iş parçacığı, CountDownLatch.await()sayım ulaşır sıfır veya başka bir kesintiye edilene kadar bekleyecektir Thread. Diğer tüm iş parçacıklarının, CountDownLatch.countDown()tamamlandıktan veya işe hazır olduktan sonra arayarak geri saymaları gerekir . sayım sıfıra ulaştığında, Threadbeklemeye başlar.

Burada sayı CountDownLatch.countDown()yöntemle azaltılır .

ThreadÇağıran await()yöntem sıfıra ilk sayısı varıncaya kadar bekler.

Sayıyı sıfırlamak için diğer iş parçacıklarının countDown()yöntemi çağırması gerekir . Sayım sıfıra ulaştığında, await()yöntemi başlatan iş parçacığı devam eder (yürütülmesini başlatır).

Dezavantajı, CountDownLatchtekrar kullanılamamasıdır: sayım sıfır olduğunda artık kullanılamaz.


Kullandığımız do new CountDownLatch(3)biz 3 konuları var olarak newFixedThreadPool tanımlanan?
Chaklader Asfak Arefe

"işlemeye başlamadan önce" işlemeye devam etmeden "olmamalı mı?
Maria Ines Parnisari

@Arefe Evet, kod bloğunuzdan geçen konu sayısı
Vishal Akkalkote

23

NikolaB bunu çok iyi açıkladı, Ancak örnek anlamaya yardımcı olacaktır, İşte basit bir örnek ...

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



     }
  }

22

Görevini tamamlamak için birden fazla iş parçacığını beklemek istediğimizde kullanılır. Konulara katılmak benzerdir.

CountDownLatch'i nerede kullanabiliriz?

Üç "A", "B" ve "C" iş parçacığımız olduğu ve yalnızca "A" ve "B" iş parçacıklarını görevlerini tamamladığında veya kısmen tamamladığında "C" iş parçacığını başlatmak istediğimiz bir senaryo düşünelim.

Gerçek dünya BT senaryosuna uygulanabilir

Yöneticinin modülleri geliştirme ekipleri (A ve B) arasında böldüğü ve sadece her iki ekip de görevlerini tamamladığında test etmek üzere KG ekibine atamak istediği bir senaryo düşünün.

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

Yukarıdaki kodun çıktısı:

Geliştirme ekibi devB'ye atanan görev

Geliştirme ekibi devA'ya atanan görev

Görev geliştirme ekibi devB tarafından tamamlandı

Görev geliştirme ekibi devA tarafından tamamlandı

KG ekibine görev tayin edildi

Görev KG ekibi tarafından tamamlandı

Burada await () yöntemi, geri sayım bayrağının 0 olmasını bekler ve countDown () yöntemi, geri sayım bayrağını 1 oranında azaltır.

JOIN sınırlaması: Yukarıdaki örnek JOIN ile de elde edilebilir, ancak JOIN iki senaryoda kullanılamaz:

  1. İş parçacıkları oluşturmak için Thread sınıfı yerine ExecutorService kullandığımızda.
  2. Geliştirme uygulamasının% 80 görevini tamamlar tamamlamaz, kodu KG ekibine teslim etmek istediği yukarıdaki örneği değiştirin. Bu, CountDownLatch uygulamasının, kısmi yürütülmesi için başka bir iş parçacığını beklemek için kullanılabilecek uygulamayı değiştirmemize izin verdiği anlamına gelir.

3

CoundDownLatch, diğer tüm iş parçacıklarının yürütülmesi bitene kadar bir iş parçacığı beklemenizi sağlar.

Sahte kod şöyle olabilir:

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

Tüm açıklamanızı kod bloğundan çıkarmak isteyebilirsiniz
Paul Lo

En iyi yorum olsa. Ben teorik açıklamalar yerine bu "noktaya" yorumları seviyorum.
renatoaraujoc

2

Böyle bir şeyin ne zaman kullanılacağına iyi bir örnek, seri bağlantı noktalarına erişen Java Basit Seri Bağlayıcı'dır. Genellikle bağlantı noktasına bir şeyler yazarsınız ve eşzamansız olarak başka bir iş parçacığında aygıt SerialPortEventListener'a yanıt verir. Genellikle, yanıtı beklemek için bağlantı noktasına yazdıktan sonra duraklatmak istersiniz. Bu senaryo için iş parçacığı kilitlerini manuel olarak kullanmak son derece zordur, ancak Geri Sayım'ı kullanmak kolaydır. Düşünmeden önce başka bir şekilde yapabilirsin, hiç düşünmediğin yarış koşullarına dikkat et !!

pseudocode:

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


2

Latch.countDown () çağrısından sonra hata ayıklama eklerseniz, bu davranışını daha iyi anlamanıza yardımcı olabilir.

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

Çıktı, Sayılanın azaldığını gösterecektir. Bu 'sayım' etkin bir geri sayım () olan karşı Başlattığın Runnable görevler (İşlemci nesneler) sayısıdır değil latch.await için çağrısıyla ilgili ana iş parçacığı engellendi dolayısıyla çağrılan ve edilmiş ().

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

2

CountDownLatch hakkındaki oracle belgelerinden :

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ı.

A CountDownLatch, belirli bir sayıyla başlatılır. awaitMevcut sayım eriştiği çağrıları nedeniyle sıfır kadar yöntemler bloke countDown()tüm bekleme lifler serbest bırakıldığı bir yöntem ve hemen bekliyoruz dönüş sonraki çağrıları. Bu tek seferlik bir olgudur - sayım sıfırlanamaz.

CountDownLatch çok yönlü bir senkronizasyon aracıdır ve bir dizi amaç için kullanılabilir.

Bir CountDownLatchsayıyla başlatılan bir başlatma, basit bir açma / kapama mandalı veya kapı işlevi görür: çağıran tüm iş parçacıkları, countDown () 'ı çağıran bir iş parçacığı tarafından açılana kadar kapıda bekler.

Bir CountDownLatchN başlatıldı N ipler biraz hareket tamamlamış veya bazı eylem N kez tamamlanıncaya kadar bir iplik beklemesi için kullanılabilir.

public void await()
           throws InterruptedException

Geçerli iş parçacığı, iş parçacığı kesilmedikçe mandal sıfıra kadar sayılana kadar bekler.

Geçerli sayı sıfırsa, bu yöntem hemen geri döner.

public void countDown()

Sayım sıfıra ulaştığında tüm bekleyen iş parçacıklarını serbest bırakarak mandalın sayısını azaltır.

Mevcut sayı sıfırdan büyükse azaltılır. Yeni sayı sıfırsa, bekleyen tüm evreler iş parçacığı zamanlaması amacıyla yeniden etkinleştirilir.

Örneğinizin açıklaması.

  1. latchDeğişken için sayıyı 3 olarak belirlediniz

    CountDownLatch latch = new CountDownLatch(3);
  2. Paylaşılan latchiş parçacığıyla paylaştınız :Processor

  3. Adresine üç Runnableörnek ProcessorgönderildiExecutorService executor
  4. Ana iş parçacığı ( App) aşağıdaki ifadeyle sayının sıfır olmasını bekliyor

     latch.await();  
  5. Processor iş parçacığı 3 saniye uyur ve sonra sayım değerini latch.countDown()
  6. İlk Processörnek, tamamlanmasından sonra mandal sayısını 2 olarak değiştirecektir latch.countDown().

  7. İkinci Processörnek, tamamlanmasından sonra mandal sayısını 1 olarak değiştirecektir latch.countDown().

  8. Üçüncü Processörnek, tamamlanmasından sonra mandal sayısını 0 olarak değiştirecektir latch.countDown().

  9. Mandaldaki sıfır sayımı ana dişin Appçıkmasına neden olurawait

  10. Uygulama programı şimdi bu çıktıyı yazdırıyor: Completed


2

Java Doc'tan gelen bu örnek kavramları net bir şekilde anlamama yardımcı oldu:

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() { ... }
}

Görsel yorumlama:

resim açıklamasını buraya girin

Açıkçası, CountDownLatchbir iş parçacığının (burada Driver) bir grup çalışan iş parçacığının (burada Worker) yürütülmesi ile tamamlanmasını beklemesine izin verir .


1

JavaDoc'ta ( https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/CountDownLatch.html ) belirtildiği gibi , CountDownLatch Java 5'de tanıtılan bir senkronizasyon yardımcısıdır. kritik bir bölüme erişimi kısıtlamak anlamına gelir. Ama daha ziyade farklı evrelerin dizilim eylemleri. CountDownLatch ile elde edilen senkronizasyon türü Join ile aynıdır. Diğer T iş parçacığı "T1", "T2", "T3" için görevlerini tamamlamak için beklemek gereken bir "M" iş parçacığı olduğunu varsayalım Java 1.5 öncesinde, bunun nasıl yapılacağı, aşağıdaki kodu çalıştıran M

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

Yukarıdaki kod, T1, T2, T3 işini tamamladıktan sonra M iş parçacığının çalışmasını sürdürmesini sağlar. T1, T2, T3 çalışmalarını herhangi bir sırada tamamlayabilir. Aynı şey, T1, T2, T3 ve M iş parçacığının aynı CountDownLatch nesnesini paylaştığı CountDownLatch ile elde edilebilir.
"M" istekleri: countDownLatch.await();
"T1", "T2", "T3" gibi countDownLatch.countdown();

Birleştirme yönteminin bir dezavantajı, M'nin T1, T2, T3 hakkında bilmesi gerektiğidir. Daha sonra eklenen yeni bir T4 iş parçacığı varsa, M de bunun farkında olmalıdır. Bu CountDownLatch ile önlenebilir. Uygulamadan sonra eylem dizisi [T1, T2, T3] olacaktır (T1, T2, T3 sırası yine de olabilir) -> [M]



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