tüm iş parçacıkları java'da çalışmalarını bitirene kadar bekleyin


94

Aynı anda web'den bazı bilgiler alan ve bir tampon sınıfında 5 farklı alanı dolduran 5 iş parçacığı olan bir uygulama yazıyorum.
Tüm iş parçacıkları işlerini bitirdiğinde arabellek verilerini doğrulamam ve bir veritabanında saklamam gerekiyor.
Bunu nasıl yapabilirim (tüm iş parçacıkları çalışmalarını bitirdiğinde uyarı alın)?


4
Thread.join , sorunu çözmenin oldukça düşük seviyeli, Java'nın kendine özgü bir yoludur. Çünkü Üstelik sorunludur Konu API kusurludur: Eğer olmadığını bilemeyiz katılmak başarıyla veya tamamlanmamış (bkz Java eşzamanlılık In Uygulama ). Bir CountDownLatch kullanmak gibi daha yüksek seviyeli soyutlama tercih edilebilir ve Java-kendine özgü zihniyetine "takılıp kalmayan" programcılar için daha doğal görünecektir. Benimle tartışma, Doug Lea ile tartış; )
Cedric Martin

Yanıtlar:


122

Benim aldığım yaklaşım , iş parçacığı havuzlarını yönetmek için bir ExecutorService kullanmaktır .

ExecutorService es = Executors.newCachedThreadPool();
for(int i=0;i<5;i++)
    es.execute(new Runnable() { /*  your task */ });
es.shutdown();
boolean finished = es.awaitTermination(1, TimeUnit.MINUTES);
// all tasks have finished or the time has been reached.

7
@Leonid, shutdown () işlevinin yaptığı tam olarak budur.
Peter Lawrey

3
while(!es.awaitTermination(1, TimeUnit.MINUTES));
Aquarius Power

3
@AquariusPower Ona daha uzun veya sonsuza kadar beklemesini söyleyebilirsin.
Peter Lawrey

1
oh .. anladım; bu yüzden döngüye tüm konuların bitmesini beklediğini söyleyen bir mesaj ekledim; Teşekkürler!
Aquarius Power

1
@PeterLawrey, aramak gerekli es.shutdown();mi? es.execute(runnableObj_ZipMaking);in tryblock ve in kullanarak bir thread çalıştırdığım bir kod yazarsam ne finallyaradım boolean finshed = es.awaitTermination(10, TimeUnit.MINUTES);. Öyleyse, sanırım bu, tüm iş parçacıkları çalışmalarını tamamlayana veya zaman aşımı gerçekleşene kadar beklemeli (ilk ne olursa olsun), varsayımım doğru mu? veya arama shutdown()zorunlu mu?
Amogh

54

Konuya gidebilirsiniz join. İş parçacığı tamamlanana kadar birleşim blokları.

for (Thread thread : threads) {
    thread.join();
}

Not joinbir atar InterruptedException. Böyle bir durumda ne yapacağınıza karar vermelisiniz (örneğin, gereksiz işlerin yapılmasını önlemek için diğer konuları iptal etmeye çalışın).


1
Bu ipler birbirine paralel mi yoksa sırayla mı çalışıyor?
James Webster

5
@JamesWebster: Paralel.
RYN

4
@James Webster: İfade t.join();, mevcut iş parçacığının iş parçacığı tsona erene kadar bloklandığı anlamına gelir . İpliği etkilemez t.
Mark Byers

1
Teşekkürler. =] Üniversitede paralellik eğitimi aldım ama öğrenmek için uğraştığım tek şey buydu! Neyse ki şu anda çok kullanmam gerekmiyor ya da kullandığımda çok karmaşık değil ya da paylaşılan kaynak yok ve engelleme kritik değil
James Webster

1
@ 4r1y4n Sağlanan kodun gerçekten paralel olup olmadığı, onunla ne yapmaya çalıştığınıza bağlıdır ve daha çok birleştirilmiş iş parçacıkları kullanarak koleksiyonlar boyunca yayılan verileri toplamakla ilgilidir. İleti dizilerine katılıyorsunuz, bu da potansiyel olarak verileri "birleştirmek" anlamına gelir. Ayrıca paralellik, eşzamanlılık anlamına gelmez. Bu, CPU'lara bağlıdır. İş parçacıkları paralel olarak çalışıyor olabilir, ancak hesaplamalar temel CPU tarafından belirlenen sırada gerçekleşiyor olabilir.

22

Çeşitli çözümlere bir göz atın.

  1. join()API, Java'nın ilk sürümlerinde tanıtılmıştır. JDK 1.5 sürümünden beri bu eşzamanlı paketle bazı iyi alternatifler mevcuttur .

  2. ExecutorService # invokeAll ()

    Her şey tamamlandığında statülerini ve sonuçlarını tutan bir Vadeli İşlemler listesi döndürerek verilen görevleri yürütür.

    Kod örneği için bu ilgili SE sorusuna bakın:

    Tüm iş parçacığı havuzunun görevlerini yapmasına izin vermek için invokeAll () nasıl kullanılır?

  3. 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 eşitleme yardımı.

    Bir CountDownLatch belirli bir sayımla başlatılır. Await yöntemleri, countDown()yöntemin çağrıları nedeniyle geçerli sayı sıfıra ulaşana kadar bloklanır , bundan sonra tüm bekleyen iş parçacıkları serbest bırakılır ve sonraki tüm await dönüş çağrıları hemen döner. Bu tek seferlik bir fenomendir - sayı sıfırlanamaz. Sayımı sıfırlayan bir sürüme ihtiyacınız varsa, CyclicBarrier kullanmayı düşünün .

    Kullanımı için bu soruya bakın CountDownLatch

    Kendi iş parçacığını ortaya çıkaran bir iş parçacığı nasıl beklenir?

  4. ForkJoinPool veya newWorkStealingPool () içinde uygulayıcıları

  5. Adresine gönderdikten sonra oluşturulan tüm Gelecek nesnelerini yineleyinExecutorService


11

Thread.join()Başkalarının önerdiği dışında , java 5 yürütme çerçevesini tanıttı. Orada Threadnesnelerle çalışmazsınız . Bunun yerine, Callableya da Runnablenesnelerinizi bir uygulayıcıya gönderirsiniz . Birden fazla görevi yerine getirmesi ve sonuçlarını sıra dışı olarak döndürmesi amaçlanan özel bir uygulayıcı var. İşte bu ExecutorCompletionService:

ExecutorCompletionService executor;
for (..) {
    executor.submit(Executors.callable(yourRunnable));
}

Ardından , geri dönecek take()başka Future<?>nesne kalmayana kadar tekrar tekrar arayabilirsiniz , bu da tümünün tamamlandığı anlamına gelir.


Senaryonuza bağlı olarak alakalı olabilecek başka bir şey de CyclicBarrier.

Bir dizi iş parçacığının hepsinin ortak bir bariyer noktasına ulaşmasını beklemesine izin veren bir senkronizasyon yardımı. CyclicBarriers, ara sıra birbirini beklemesi gereken sabit boyutlu bir iş parçacığı partisini içeren programlarda kullanışlıdır. Bariyere döngüsel denir çünkü bekleyen ipler serbest bırakıldıktan sonra yeniden kullanılabilir.


Bu yakın, ama yine de birkaç ayarlama yapacağım. executor.submitdöndürür a Future<?>. Bu futures'ları bir listeye eklerim ve sonra gether bir geleceği çağıran listeyi gözden geçirirdim .
Ray

Ayrıca, Executorsörneğin Executors.newCachedThreadPool(veya benzeri) kullanarak bir kurucu başlatabilirsiniz
Ray

10

Diğer bir olasılık, CountDownLatchbasit durumlar için yararlı olan nesnedir: iş parçacığı sayısını önceden bildiğiniz için, onu ilgili sayı ile başlatır ve nesnenin referansını her iş parçacığına iletirsiniz.
Görevi tamamlandıktan sonra, her iş parçacığı CountDownLatch.countDown()dahili sayacı azaltan çağırır . Ana iş parçacığı, diğerlerini başlattıktan sonra, CountDownLatch.await()engelleme çağrısını yapmalıdır . Dahili sayaç 0'a ulaşır ulaşmaz serbest bırakılacaktır.

Bu nesne ile bir InterruptedExceptionde fırlatılabileceğine dikkat edin .


8

Diğer bazı iş parçacıkları çalışmalarını tamamlayana kadar Ana İş Parçacığını bekleyin / engelleyin.

As @Ravindra babuçeşitli şekillerde elde ancak örneklerle gösteren edilebileceğini söyledi.

  • java.lang.Thread. Join () Beri: 1.0

    public static void joiningThreads() throws InterruptedException {
        Thread t1 = new Thread( new LatchTask(1, null), "T1" );
        Thread t2 = new Thread( new LatchTask(7, null), "T2" );
        Thread t3 = new Thread( new LatchTask(5, null), "T3" );
        Thread t4 = new Thread( new LatchTask(2, null), "T4" );
    
        // Start all the threads
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    
        // Wait till all threads completes
        t1.join();
        t2.join();
        t3.join();
        t4.join();
    }
    
  • java.util.concurrent.CountDownLatch Beri: 1.5

    • .countDown() «Mandal grubu sayısını azaltır.
    • .await() «Bekleme yöntemleri, geçerli sayım sıfıra ulaşana kadar bloke edilir.

    Oluşturduğunuz Eğer latchGroupCount = 4o countDown()0. So sayımı yapmak 4 kez çağrılabilir gerektiğini await()engelleme konuları yayınlayacak.

    public static void latchThreads() throws InterruptedException {
        int latchGroupCount = 4;
        CountDownLatch latch = new CountDownLatch(latchGroupCount);
        Thread t1 = new Thread( new LatchTask(1, latch), "T1" );
        Thread t2 = new Thread( new LatchTask(7, latch), "T2" );
        Thread t3 = new Thread( new LatchTask(5, latch), "T3" );
        Thread t4 = new Thread( new LatchTask(2, latch), "T4" );
    
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    
        //latch.countDown();
    
        latch.await(); // block until latchGroupCount is 0.
    }
    

Dişli sınıfının örnek kodu LatchTask. Yaklaşım kullanımını joiningThreads(); ve latchThreads();ana yöntemi test etmek .

class LatchTask extends Thread {
    CountDownLatch latch;
    int iterations = 10;
    public LatchTask(int iterations, CountDownLatch latch) {
        this.iterations = iterations;
        this.latch = latch;
    }

    @Override
    public void run() {
        String threadName = Thread.currentThread().getName();
        System.out.println(threadName + " : Started Task...");

        for (int i = 0; i < iterations; i++) {
            System.out.println(threadName + " : " + i);
            MainThread_Wait_TillWorkerThreadsComplete.sleep(1);
        }
        System.out.println(threadName + " : Completed Task");
        // countDown() « Decrements the count of the latch group.
        if(latch != null)
            latch.countDown();
    }
}
  • CyclicBarriers Bir dizi iş parçacığının ortak bir bariyer noktasına ulaşmasını beklemesine izin veren bir senkronizasyon yardımı. CyclicBarriers, ara sıra birbirini beklemesi gereken sabit boyutlu bir iş parçacığı partisini içeren programlarda kullanışlıdır. Bariyere döngüsel denir çünkü bekleyen ipler serbest bırakıldıktan sonra yeniden kullanılabilir.
    CyclicBarrier barrier = new CyclicBarrier(3);
    barrier.await();
    
    Örneğin, bu Concurrent_ParallelNotifyies sınıfına bakın .

  • Yürütme çerçevesi: Bir iş parçacığı havuzu oluşturmak için ExecutorService'i kullanabilir ve Future ile zaman uyumsuz görevlerin ilerlemesini izleyebiliriz .

    • submit(Runnable), submit(Callable)Future Object döndürür. future.get()Fonksiyonu kullanarak , çalışan iş parçacıkları işini tamamlayana kadar ana iş parçacığını bloke edebiliriz.

    • invokeAll(...) - her bir Çağrılabilir'in yürütme sonuçlarını elde edebileceğiniz Gelecek nesnelerinin bir listesini döndürür.

Runnable, Callable with Executor framework'ü kullanma örneğini bulun .


@Ayrıca bakınız


7

Siz yapıyorsunuz

for (Thread t : new Thread[] { th1, th2, th3, th4, th5 })
    t.join()

Bu for döngüsünden sonra, tüm iş parçacıklarının işlerini bitirdiğinden emin olabilirsiniz.


4

Thread nesnelerini bir koleksiyonda (List veya Set gibi) depolayın, ardından thread'ler başlatıldığında koleksiyon boyunca döngü yapın ve Threads üzerinde join () 'ı çağırın .



2

OP'nin problemi ile ilgili olmasa da, tam olarak bir iş parçacığı ile senkronizasyon (daha doğrusu bir buluşma noktası) ile ilgileniyorsanız, bir Değiştirici kullanabilirsiniz.

Benim durumumda, çocuk iş parçacığı bir şey yapana kadar ana iş parçacığını duraklatmam gerekiyordu, örneğin başlatılmasını tamamladı. Bir CountDownLatch da etkili olabilmektedir.



1

bunu dene, işe yarayacak.

  Thread[] threads = new Thread[10];

  List<Thread> allThreads = new ArrayList<Thread>();

  for(Thread thread : threads){

        if(null != thread){

              if(thread.isAlive()){

                    allThreads.add(thread);

              }

        }

  }

  while(!allThreads.isEmpty()){

        Iterator<Thread> ite = allThreads.iterator();

        while(ite.hasNext()){

              Thread thread = ite.next();

              if(!thread.isAlive()){

                   ite.remove();
              }

        }

   }

1

Benzer bir sorun yaşadım ve sonunda Java 8 parallelStream kullandım.

requestList.parallelStream().forEach(req -> makeRequest(req));

Çok basit ve okunabilir. Perde arkasında, varsayılan JVM'nin çatal birleştirme havuzunu kullanıyor, bu da devam etmeden önce tüm iş parçacıklarının bitmesini bekleyeceği anlamına geliyor. Benim durumum için temiz bir çözümdü, çünkü uygulamamdaki tek parallelStream buydu. Aynı anda çalışan birden fazla paralel Akışın varsa, lütfen aşağıdaki bağlantıyı okuyun.

Paralel akışlar hakkında daha fazla bilgiyi burada bulabilirsiniz .


1

Birkaç İş Parçacığının bitmesini beklemek için küçük bir yardımcı yöntem oluşturdum:

public static void waitForThreadsToFinish(Thread... threads) {
        try {
            for (Thread thread : threads) {
                thread.join();
            }
        }
        catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

0

Mevcut cevaplar olabilir dedi join() her konu .

Ancak dizi / listeyi elde etmenin birkaç yolu vardır:

  • Konu Oluşturma sırasında bir listeye ekleyin.
  • ThreadGroupKonuları yönetmek için kullanın .

Aşağıdaki kod ThreadGruopyaklaşımı kullanacaktır . Önce bir grup oluşturur, daha sonra her iş parçacığı oluştururken oluşturucuda grubu belirtir, daha sonra iş parçacığı dizisiniThreadGroup.enumerate()


Kod

SyncBlockLearn.java

import org.testng.Assert;
import org.testng.annotations.Test;

/**
 * synchronized block - learn,
 *
 * @author eric
 * @date Apr 20, 2015 1:37:11 PM
 */
public class SyncBlockLearn {
    private static final int TD_COUNT = 5; // thread count
    private static final int ROUND_PER_THREAD = 100; // round for each thread,
    private static final long INC_DELAY = 10; // delay of each increase,

    // sync block test,
    @Test
    public void syncBlockTest() throws InterruptedException {
        Counter ct = new Counter();
        ThreadGroup tg = new ThreadGroup("runner");

        for (int i = 0; i < TD_COUNT; i++) {
            new Thread(tg, ct, "t-" + i).start();
        }

        Thread[] tArr = new Thread[TD_COUNT];
        tg.enumerate(tArr); // get threads,

        // wait all runner to finish,
        for (Thread t : tArr) {
            t.join();
        }

        System.out.printf("\nfinal count: %d\n", ct.getCount());
        Assert.assertEquals(ct.getCount(), TD_COUNT * ROUND_PER_THREAD);
    }

    static class Counter implements Runnable {
        private final Object lkOn = new Object(); // the object to lock on,
        private int count = 0;

        @Override
        public void run() {
            System.out.printf("[%s] begin\n", Thread.currentThread().getName());

            for (int i = 0; i < ROUND_PER_THREAD; i++) {
                synchronized (lkOn) {
                    System.out.printf("[%s] [%d] inc to: %d\n", Thread.currentThread().getName(), i, ++count);
                }
                try {
                    Thread.sleep(INC_DELAY); // wait a while,
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            System.out.printf("[%s] end\n", Thread.currentThread().getName());
        }

        public int getCount() {
            return count;
        }
    }
}

Ana iş parçacığı, gruptaki tüm iş parçacıklarının bitmesini bekleyecektir.


-1

Bunu ana iş parçacığınızda kullanın: while (! Executor.isTerminated ()); Yürütücü hizmetinden tüm iş parçacıklarını başlattıktan sonra bu kod satırını koyun. Bu, ana iş parçacığını yalnızca yürütücüler tarafından başlatılan tüm iş parçacıkları bittikten sonra başlatır. Executor.shutdown () 'ı çağırdığınızdan emin olun; yukarıdaki döngüden önce.


Bu aktif beklemedir ve CPU'nun sürekli olarak boş bir döngü çalıştırmasına neden olur. Çok savurgan.
Adam Michalik
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.