Bir dizi iş parçacığının tamamlanması için nasıl beklenir?


109

Tüm iş parçacıklı sürecin bitmesini beklemenin bir yolu nedir? Örneğin, şöyle diyelim:

public class DoSomethingInAThread implements Runnable{

    public static void main(String[] args) {
        for (int n=0; n<1000; n++) {
            Thread t = new Thread(new DoSomethingInAThread());
            t.start();
        }
        // wait for all threads' run() methods to complete before continuing
    }

    public void run() {
        // do something here
    }


}

Bunu nasıl değiştiririm, böylece main()yöntem, tüm iş parçacıklarının run()yöntemleri çıkana kadar yorumda duraklatılır ? Teşekkürler!

Yanıtlar:


163

Tüm konuları bir diziye koyarsınız, hepsini başlatırsınız ve sonra bir döngü elde edersiniz

for(i = 0; i < threads.length; i++)
  threads[i].join();

Her bir birleştirme, ilgili iş parçacığı tamamlanana kadar engellenecektir. İleti dizileri, onlara katıldığınızdan farklı bir sırada tamamlanabilir, ancak bu bir sorun değildir: döngüden çıkıldığında, tüm iş parçacıkları tamamlanır.


1
@Mykola: Bir iş parçacığı grubu kullanmanın avantajı tam olarak nedir? API'nin orada olması, onu kullanmanız gerektiği anlamına gelmez ...
Martin - Löwis

2
Bakınız: "Bir iş parçacığı grubu, bir dizi iş parçacığını temsil eder." Bu, bu kullanım durumu için anlamsal olarak doğrudur! Ve: "Bir iş parçacığının kendi iş parçacığı grubu hakkındaki bilgilere erişmesine izin verilir"
Martin K.

4
"Etkili Java" kitabı iş parçacığı gruplarından kaçınmayı tavsiye ediyor (madde 73).
Bastien Léonard

2
Etkili Java'da bahsedilen hatalar Java 6'da düzeltilmiş olmalıydı. Daha yeni java sürümleri bir kısıtlama değilse, iş parçacığı sorunlarını çözmek için Vadeli İşlemleri kullanmak daha iyidir. Martin v. Löwis: Haklısınız. Bu sorunla ilgili değil, ancak bir Nesneden (ExecutorService gibi) çalışan iş parçacıkları hakkında daha fazla bilgi almak güzel. Bir sorunu çözmek için verilen özellikleri kullanmanın güzel olduğunu düşünüyorum; belki gelecekte daha fazla esnekliğe (iş parçacığı bilgisi) ihtiyacınız olacak. Ayrıca eski JDK'lardaki eski buggy sınıflarından bahsetmek de doğru.
Martin K.

5
ThreadGroup, grup düzeyinde bir birleştirme uygulamaz, bu nedenle insanların ThreadGroup'u neden zorlaması biraz kafa karıştırıcıdır. İnsanlar gerçekten döndürme kilitleri kullanıyor ve grubun activeCount'unu sorguluyor mu? Beni bu şekilde yapmanın, tüm iş parçacıklarında birleştirme çağırmakla karşılaştırıldığında herhangi bir şekilde daha iyi olduğuna beni ikna etmeniz zor olacaktır.

41

Tek yönlü bir yapmak olacaktır Listarasında Threadlistesine eklerken, s oluşturabilir ve her iş parçacığı başlatın. Her şey başlatıldığında, listeye geri dönün ve join()her birini arayın . İş parçacığının hangi sırada çalışmayı bitirdiği önemli değil, bilmeniz gereken tek şey, ikinci döngünün çalıştırılması bittiğinde, her iş parçacığının tamamlanmış olacağıdır.

Daha iyi bir yaklaşım, bir ExecutorService ve bununla ilişkili yöntemleri kullanmaktır:

List<Callable> callables = ... // assemble list of Callables here
                               // Like Runnable but can return a value
ExecutorService execSvc = Executors.newCachedThreadPool();
List<Future<?>> results = execSvc.invokeAll(callables);
// Note: You may not care about the return values, in which case don't
//       bother saving them

Bir ExecutorService (ve Java 5'in eşzamanlılık hizmetlerinden gelen tüm yeni şeyler ) kullanmak inanılmaz derecede esnektir ve yukarıdaki örnek yüzeyi neredeyse hiç çizmez.


ThreadGroup gitmenin yoludur! Değişebilir bir Listeyle başınız belaya girecek (senkronizasyon)
Martin K.

3
Ne? Nasıl başın belaya girer? O listeyi değiştirmez böylece sürece, başlatma yapıyor ipliğine (sadece readble) sadece değişken var ise , içinden 's cezası iterating.
Adam Batkin

Nasıl kullandığınıza bağlıdır. Çağıran sınıfı bir iş parçacığında kullanırsanız, sorun yaşarsınız.
Martin K.

27
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class DoSomethingInAThread implements Runnable
{
   public static void main(String[] args) throws ExecutionException, InterruptedException
   {
      //limit the number of actual threads
      int poolSize = 10;
      ExecutorService service = Executors.newFixedThreadPool(poolSize);
      List<Future<Runnable>> futures = new ArrayList<Future<Runnable>>();

      for (int n = 0; n < 1000; n++)
      {
         Future f = service.submit(new DoSomethingInAThread());
         futures.add(f);
      }

      // wait for all tasks to complete before continuing
      for (Future<Runnable> f : futures)
      {
         f.get();
      }

      //shut down the executor service so that this thread can exit
      service.shutdownNow();
   }

   public void run()
   {
      // do something here
   }
}

bir cazibe gibi çalıştı ... Birden fazla çerezdeki sorunlar nedeniyle aynı anda çalışmaması gereken iki dizi iş parçacığım var.
Örneğinizi

@Dantalian - Runnable sınıfınızda (muhtemelen run yönteminde), meydana gelen istisnaları yakalamak ve bunları yerel olarak depolamak (veya bir hata mesajını / koşulu depolamak) istersiniz. Örnekte, f.get (), ExecutorService'e gönderdiğiniz nesnenizi döndürür. Nesneniz, herhangi bir istisna / hata koşulunu almak için bir yönteme sahip olabilir. Sağlanan örneği nasıl değiştirdiğinize bağlı olarak, nesneyi f.get () tarafından beklenen türe çevirmeniz gerekebilir.
jt.

12

Bunun yerine, join()eski bir API olan CountDownLatch'i kullanabilirsiniz . İhtiyaçlarınızı karşılamak için kodunuzu aşağıdaki gibi değiştirdim.

import java.util.concurrent.*;
class DoSomethingInAThread implements Runnable{
    CountDownLatch latch;
    public DoSomethingInAThread(CountDownLatch latch){
        this.latch = latch;
    } 
    public void run() {
        try{
            System.out.println("Do some thing");
            latch.countDown();
        }catch(Exception err){
            err.printStackTrace();
        }
    }
}

public class CountDownLatchDemo {
    public static void main(String[] args) {
        try{
            CountDownLatch latch = new CountDownLatch(1000);
            for (int n=0; n<1000; n++) {
                Thread t = new Thread(new DoSomethingInAThread(latch));
                t.start();
            }
            latch.await();
            System.out.println("In Main thread after completion of 1000 threads");
        }catch(Exception err){
            err.printStackTrace();
        }
    }
}

Açıklama :

  1. CountDownLatch Gereksiniminize göre verilen 1000 sayısı ile başlatılmıştır.

  2. Her bir çalışan iş parçacığı DoSomethingInAThread , CountDownLatchyapıcıda iletileni azaltır .

  3. CountDownLatchDemo await()Sayım sıfır olana kadar ana iş parçacığı . Sayım sıfır olduğunda, çıktıda satırın altına ineceksiniz.

    In Main thread after completion of 1000 threads

Oracle belgeleri sayfasından daha fazla bilgi

public void await()
           throws InterruptedException

İplik kesilmedikçe, mevcut iş parçacığının mandal sıfıra geri sayana kadar beklemesine neden olur.

Diğer seçenekler için ilgili SE sorusuna bakın:

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


8

Thread sınıfından tamamen kaçının ve bunun yerine java.util.concurrent içinde sağlanan daha yüksek soyutlamaları kullanın

ExecutorService sınıfı, yalnızca istediğiniz şeyi yapıyor gibi görünen invokeAll yöntemini sağlar .


6

Kullanmayı düşünün java.util.concurrent.CountDownLatch. Javadocs örnekler


İplikler için bir mandaldır, mandal kilidi bir geri sayımla çalışır. İş parçacığınızın run () yönteminde, bir CountDownLatch'in 0'a geri sayımına ulaşmasını beklemeyi açıkça beyan edin. Aynı CountDownLatch'i birden fazla iş parçacığında aynı anda serbest bırakmak için kullanabilirsiniz. İhtiyacınız olup olmadığını bilmiyorum, sadece bahsetmek istedim çünkü çok iş parçacıklı bir ortamda çalışırken yararlıdır.
Pablo Cavalieri

Belki de bu açıklamayı cevabınızın gövdesine eklemelisiniz?
Aaron Hall

Javadoc'taki örnekler çok açıklayıcı, bu yüzden hiç eklemedim. docs.oracle.com/javase/7/docs/api/java/util/concurrent/… . İlk örnekte, tüm İşçiler iş parçacığı eşzamanlı olarak yayımlanır çünkü CountdownLatch startSignal'in startSignal.countDown () içinde sıfıra ulaşmasını beklerler. Daha sonra mian iş parçacığı, doneSignal.await () komutunu kullanarak tüm işler bitene kadar bekler. doneSignal, her bir çalışanda değerini düşürür.
Pablo Cavalieri

6

Martin K'nin önerdiği java.util.concurrent.CountDownLatchgibi bunun için daha iyi bir çözüm gibi görünüyor. Sadece aynısı için bir örnek eklemek

     public class CountDownLatchDemo
{

    public static void main (String[] args)
    {
        int noOfThreads = 5;
        // Declare the count down latch based on the number of threads you need
        // to wait on
        final CountDownLatch executionCompleted = new CountDownLatch(noOfThreads);
        for (int i = 0; i < noOfThreads; i++)
        {
            new Thread()
            {

                @Override
                public void run ()
                {

                    System.out.println("I am executed by :" + Thread.currentThread().getName());
                    try
                    {
                        // Dummy sleep
                        Thread.sleep(3000);
                        // One thread has completed its job
                        executionCompleted.countDown();
                    }
                    catch (InterruptedException e)
                    {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }

            }.start();
        }

        try
        {
            // Wait till the count down latch opens.In the given case till five
            // times countDown method is invoked
            executionCompleted.await();
            System.out.println("All over");
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
    }

}

4

İhtiyaçlarınıza bağlı olarak, java.util.concurrent paketindeki CountDownLatch ve CyclicBarrier sınıflarını da kontrol etmek isteyebilirsiniz. İş parçacıklarınızın birbirini beklemesini istiyorsanız veya iş parçacıklarınızın yürütme şekli üzerinde daha ayrıntılı denetim istiyorsanız (örneğin, başka bir iş parçacığının bir durum ayarlaması için iç yürütmede beklemek) yararlı olabilirler. Döngünüzde ilerledikçe tek tek başlatmak yerine, tüm iş parçacıklarınızın aynı anda başlaması için sinyal vermek için bir CountDownLatch de kullanabilirsiniz. Standart API belgelerinde bunun bir örneği vardır, ayrıca tüm iş parçacıklarının yürütülmesini tamamlamasını beklemek için başka bir CountDownLatch kullanır.



1

İlk for döngüsünün içinde iş parçacığı nesnesini oluşturun.

for (int i = 0; i < threads.length; i++) {
     threads[i] = new Thread(new Runnable() {
         public void run() {
             // some code to run in parallel
         }
     });
     threads[i].start();
 }

Ve sonra buradaki herkesin söylediği.

for(i = 0; i < threads.length; i++)
  threads[i].join();

0

Tam olarak nasıl yapmayı önerdiğinden emin değilim. Bir döngüde activeCount'u yoklamayı teklif ederseniz: bu kötüdür, çünkü beklemek meşguldür (anketler arasında uyuyor olsanız bile - o zaman iş ve yanıt verme arasında bir denge elde edersiniz).
Martin / Löwis

@Martin v. Löwis: "Join sadece tek bir iş parçacığı bekleyecek. Daha iyi bir çözüm java.util.concurrent.CountDownLatch olabilir. Mandalı basitçe çalışan iş parçacığı sayısına ayarlanmış sayıyla başlatın. Her çalışan iş parçacığı çağırmalıdır countDown () çıkmadan hemen önce ve ana iş parçacığı, sayaç sıfıra ulaşana kadar bloke edecek olan await () 'yi çağırır. join () ile ilgili sorun, dinamik olarak daha fazla iş parçacığı eklemeye başlayamamanızdır. Liste patlayacaktır. Eşzamanlı Değişiklik ile. " Çözümünüz Sorun için iyi çalışıyor, ancak genel amaç için değil.
Martin K.

0

CountDownLatch'e alternatif olarak CyclicBarrier'ı da kullanabilirsiniz, örn.

public class ThreadWaitEx {
    static CyclicBarrier barrier = new CyclicBarrier(100, new Runnable(){
        public void run(){
            System.out.println("clean up job after all tasks are done.");
        }
    });
    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            Thread t = new Thread(new MyCallable(barrier));
            t.start();
        }       
    }

}    

class MyCallable implements Runnable{
    private CyclicBarrier b = null;
    public MyCallable(CyclicBarrier b){
        this.b = b;
    }
    @Override
    public void run(){
        try {
            //do something
            System.out.println(Thread.currentThread().getName()+" is waiting for barrier after completing his job.");
            b.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (BrokenBarrierException e) {
            e.printStackTrace();
        }
    }       
}

Bu durumda CyclicBarrier'ı kullanmak için barrier.await () son ifade olmalıdır, yani iş parçacığınızın işi bittiğinde. CyclicBarrier, reset () yöntemi ile tekrar kullanılabilir. Javadocs'tan alıntı yapmak için:

CyclicBarrier, partideki son iş parçacığı geldikten sonra, ancak herhangi bir iş parçacığı serbest bırakılmadan önce engel noktası başına bir kez çalıştırılan isteğe bağlı bir Çalıştırılabilir komutunu destekler. Bu bariyer eylemi, taraflardan herhangi biri devam etmeden önce paylaşılan durumu güncellemek için kullanışlıdır.


Bunun CyclicBarrier için iyi bir örnek olduğunu sanmıyorum. Neden Thread.sleep () çağrısı kullanıyorsunuz?
Guenther

@Guenther - evet, kodu gereksinimi karşılayacak şekilde değiştirdim.
shailendra1118

CyclicBarrier, CountDownLatch'e bir alternatif değildir. İş parçacıkları tekrar tekrar geri saymak zorunda kaldığında, bir CyclicBarrier oluşturmalısınız, aksi takdirde varsayılan olarak CountDownLatch'e (aksi takdirde ek Yürütme soyutlaması gerekmedikçe, bu noktada daha yüksek düzey, Hizmetler'e bakmanız gerekir).
Elysiumplain

0

join()Bana yardımcı olmadı. Kotlin'deki bu örneğe bakın:

    val timeInMillis = System.currentTimeMillis()
    ThreadUtils.startNewThread(Runnable {
        for (i in 1..5) {
            val t = Thread(Runnable {
                Thread.sleep(50)
                var a = i
                kotlin.io.println(Thread.currentThread().name + "|" + "a=$a")
                Thread.sleep(200)
                for (j in 1..5) {
                    a *= j
                    Thread.sleep(100)
                    kotlin.io.println(Thread.currentThread().name + "|" + "$a*$j=$a")
                }
                kotlin.io.println(Thread.currentThread().name + "|TaskDurationInMillis = " + (System.currentTimeMillis() - timeInMillis))
            })
            t.start()
        }
    })

Sonuç:

Thread-5|a=5
Thread-1|a=1
Thread-3|a=3
Thread-2|a=2
Thread-4|a=4
Thread-2|2*1=2
Thread-3|3*1=3
Thread-1|1*1=1
Thread-5|5*1=5
Thread-4|4*1=4
Thread-1|2*2=2
Thread-5|10*2=10
Thread-3|6*2=6
Thread-4|8*2=8
Thread-2|4*2=4
Thread-3|18*3=18
Thread-1|6*3=6
Thread-5|30*3=30
Thread-2|12*3=12
Thread-4|24*3=24
Thread-4|96*4=96
Thread-2|48*4=48
Thread-5|120*4=120
Thread-1|24*4=24
Thread-3|72*4=72
Thread-5|600*5=600
Thread-4|480*5=480
Thread-3|360*5=360
Thread-1|120*5=120
Thread-2|240*5=240
Thread-1|TaskDurationInMillis = 765
Thread-3|TaskDurationInMillis = 765
Thread-4|TaskDurationInMillis = 765
Thread-5|TaskDurationInMillis = 765
Thread-2|TaskDurationInMillis = 765

Şimdi join()iş parçacıkları için kullanmama izin verin :

    val timeInMillis = System.currentTimeMillis()
    ThreadUtils.startNewThread(Runnable {
        for (i in 1..5) {
            val t = Thread(Runnable {
                Thread.sleep(50)
                var a = i
                kotlin.io.println(Thread.currentThread().name + "|" + "a=$a")
                Thread.sleep(200)
                for (j in 1..5) {
                    a *= j
                    Thread.sleep(100)
                    kotlin.io.println(Thread.currentThread().name + "|" + "$a*$j=$a")
                }
                kotlin.io.println(Thread.currentThread().name + "|TaskDurationInMillis = " + (System.currentTimeMillis() - timeInMillis))
            })
            t.start()
            t.join()
        }
    })

Ve sonuç:

Thread-1|a=1
Thread-1|1*1=1
Thread-1|2*2=2
Thread-1|6*3=6
Thread-1|24*4=24
Thread-1|120*5=120
Thread-1|TaskDurationInMillis = 815
Thread-2|a=2
Thread-2|2*1=2
Thread-2|4*2=4
Thread-2|12*3=12
Thread-2|48*4=48
Thread-2|240*5=240
Thread-2|TaskDurationInMillis = 1568
Thread-3|a=3
Thread-3|3*1=3
Thread-3|6*2=6
Thread-3|18*3=18
Thread-3|72*4=72
Thread-3|360*5=360
Thread-3|TaskDurationInMillis = 2323
Thread-4|a=4
Thread-4|4*1=4
Thread-4|8*2=8
Thread-4|24*3=24
Thread-4|96*4=96
Thread-4|480*5=480
Thread-4|TaskDurationInMillis = 3078
Thread-5|a=5
Thread-5|5*1=5
Thread-5|10*2=10
Thread-5|30*3=30
Thread-5|120*4=120
Thread-5|600*5=600
Thread-5|TaskDurationInMillis = 3833

Kullandığımız zaman açıkça görüldüğü gibi join:

  1. İplikler sırayla çalışıyor.
  2. İlk örnek 765 Milisaniye, ikinci örnek 3833 Milisaniye sürüyor.

Diğer iş parçacıklarının engellenmesini önlemeye yönelik çözümümüz bir ArrayList oluşturmaktı:

val threads = ArrayList<Thread>()

Şimdi yeni bir iş parçacığı başlatmak istediğimizde, onu en çok ArrayList'e ekleriz:

addThreadToArray(
    ThreadUtils.startNewThread(Runnable {
        ...
    })
)

addThreadToArrayfonksiyon:

@Synchronized
fun addThreadToArray(th: Thread) {
    threads.add(th)
}

startNewThreadfunstion:

fun startNewThread(runnable: Runnable) : Thread {
    val th = Thread(runnable)
    th.isDaemon = false
    th.priority = Thread.MAX_PRIORITY
    th.start()
    return th
}

Konuların tamamlandığını, ihtiyaç duyulan her yerde aşağıdaki gibi kontrol edin:

val notAliveThreads = ArrayList<Thread>()
for (t in threads)
    if (!t.isAlive)
        notAliveThreads.add(t)
threads.removeAll(notAliveThreads)
if (threads.size == 0){
    // The size is 0 -> there is no alive threads.
}
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.