จะรอให้เธรดจำนวนหนึ่งเสร็จสมบูรณ์ได้อย่างไร?


109

มีวิธีใดเพียงแค่รอให้กระบวนการเธรดทั้งหมดเสร็จสิ้น ตัวอย่างเช่นสมมติว่าฉันมี:

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
    }


}

ฉันจะแก้ไขสิ่งนี้ได้อย่างไรเพื่อให้main()เมธอดหยุดที่ข้อคิดเห็นจนกว่าrun()วิธีการของเธรดทั้งหมดจะออก ขอบคุณ!

คำตอบ:


163

คุณใส่เธรดทั้งหมดในอาร์เรย์เริ่มต้นทั้งหมดแล้วมีลูป

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

การเข้าร่วมแต่ละครั้งจะบล็อกจนกว่าเธรดที่เกี่ยวข้องจะเสร็จสิ้น เธรดอาจเสร็จสมบูรณ์ในลำดับที่แตกต่างจากที่คุณเข้าร่วม แต่นั่นไม่ใช่ปัญหา: เมื่อลูปออกจากเธรดทั้งหมดจะเสร็จสมบูรณ์


1
@Mykola: สิ่งที่ว่าเป็นข้อได้เปรียบของการใช้กลุ่มด้ายหรือไม่ เพียงเพราะมี API ไม่ได้หมายความว่าคุณต้องใช้ ...
Martin v. Löwis

2
โปรดดู: "กลุ่มเธรดแสดงถึงชุดของเธรด" นี่เป็นความหมายที่ถูกต้องสำหรับกรณีการใช้งานนี้! และ: "เธรดได้รับอนุญาตให้เข้าถึงข้อมูลเกี่ยวกับกลุ่มเธรดของตนเอง"
Martin K.

4
หนังสือ“ Effective Java” แนะนำให้หลีกเลี่ยงกลุ่มเธรด (ข้อ 73)
Bastien Léonard

2
ข้อบกพร่องที่กล่าวถึงใน Effective Java ควรได้รับการแก้ไขแล้วใน Java 6 หากเวอร์ชัน java ที่ใหม่กว่าไม่ใช่ข้อ จำกัด ควรใช้ Futures เพื่อแก้ปัญหาเธรด Martin v. Löwis: คุณพูดถูก ไม่เกี่ยวข้องกับปัญหานั้น แต่เป็นการดีที่จะได้รับข้อมูลเพิ่มเติมเกี่ยวกับเธรดที่กำลังทำงานอยู่จากอ็อบเจ็กต์เดียว (เช่น ExecutorService) ฉันคิดว่ามันเป็นเรื่องดีที่จะใช้คุณสมบัติที่กำหนดเพื่อแก้ปัญหา บางทีคุณอาจต้องการความยืดหยุ่นมากขึ้น (ข้อมูลเธรด) ในอนาคต นอกจากนี้ยังถูกต้องที่จะพูดถึงคลาส buggy เก่าใน JDK ที่เก่ากว่า
Martin K.

5
ThreadGroup ไม่ได้ใช้การเข้าร่วมระดับกลุ่มดังนั้นทำไมผู้คนถึงผลักดัน ThreadGroup จึงทำให้งง ผู้คนใช้สปินล็อกและค้นหา activeCount ของกลุ่มจริงๆหรือไม่? คุณคงยากที่จะโน้มน้าวฉันว่าการทำเช่นนั้นดีกว่าในทางใดทางหนึ่งเมื่อเทียบกับการโทรเข้าร่วมในทุกหัวข้อ

41

วิธีหนึ่งคือสร้าง a ListของThreadสร้างและเปิดแต่ละเธรดในขณะที่เพิ่มลงในรายการ เมื่อเปิดตัวทุกอย่างแล้วให้วนกลับไปที่รายการและโทรหาjoin()แต่ละคน ไม่สำคัญว่าเธรดจะทำงานในลำดับใดสิ่งที่คุณต้องรู้ก็คือเมื่อลูปที่สองทำงานเสร็จทุกเธรดจะเสร็จสมบูรณ์

แนวทางที่ดีกว่าคือการใช้ExecutorServiceและวิธีการที่เกี่ยวข้อง:

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

การใช้ ExecutorService (และสิ่งใหม่ ๆ ทั้งหมดจากยูทิลิตี้การทำงานพร้อมกันของ Java 5 ) มีความยืดหยุ่นอย่างไม่น่าเชื่อและตัวอย่างข้างต้นแทบจะไม่ทำให้พื้นผิวเป็นรอยเลย


ThreadGroup คือหนทางที่จะไป! ด้วยรายการที่ไม่แน่นอนคุณจะมีปัญหา (การซิงโครไนซ์)
Martin K.

3
อะไร? คุณจะเดือดร้อนอย่างไร? มันไม่แน่นอน (อ่านได้เท่านั้น) โดยเธรดที่กำลังทำการเปิดตัวดังนั้นตราบใดที่มันไม่ได้แก้ไขรายการในขณะที่ทำซ้ำมันก็ใช้ได้
Adam Batkin

ขึ้นอยู่กับว่าคุณใช้มันอย่างไร หากคุณจะใช้คลาสการโทรในเธรดคุณจะมีปัญหา
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
   }
}

ทำงานได้อย่างมีเสน่ห์ ... ฉันมีเธรดสองชุดซึ่งไม่ควรทำงานพร้อมกันเนื่องจากปัญหาเกี่ยวกับคุกกี้หลายตัว ผมใช้ตัวอย่างของคุณเพื่อเรียกใช้ชุดข้อความทีละชุด .. ขอบคุณที่แบ่งปันความรู้ ...
arn-arn

@Dantalian - ในคลาส Runnable ของคุณ (น่าจะเป็นในวิธีการรัน) คุณต้องการจับข้อยกเว้นใด ๆ ที่เกิดขึ้นและจัดเก็บไว้ในเครื่อง (หรือจัดเก็บข้อความ / เงื่อนไขแสดงข้อผิดพลาด) ในตัวอย่าง f.get () ส่งคืนอ็อบเจ็กต์ของคุณที่คุณส่งไปยัง ExecutorService วัตถุของคุณอาจมีวิธีการดึงข้อยกเว้น / เงื่อนไขข้อผิดพลาดใด ๆ ขึ้นอยู่กับวิธีที่คุณแก้ไขตัวอย่างที่ให้ไว้คุณอาจต้องโยนวัตถุที่หันโดย f.get () ไปยังประเภทที่คุณคาดหวัง
jt.

12

แทนjoin()ซึ่งเป็น API เก่าคุณสามารถใช้CountDownLatch ฉันได้แก้ไขรหัสของคุณตามด้านล่างเพื่อให้เป็นไปตามความต้องการของคุณ

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

คำอธิบาย :

  1. CountDownLatch ได้รับการเริ่มต้นด้วยการนับ 1,000 ตามความต้องการของคุณ

  2. เธรดของผู้ปฏิบัติงานแต่ละชุดDoSomethingInAThread จะลดค่าCountDownLatchที่ส่งผ่านไปในตัวสร้าง

  3. เธรดหลักCountDownLatchDemo await()จนกว่าจำนวนจะกลายเป็นศูนย์ เมื่อการนับกลายเป็นศูนย์คุณจะได้ผลลัพธ์ด้านล่างบรรทัด

    In Main thread after completion of 1000 threads

ข้อมูลเพิ่มเติมจากหน้าเอกสารของ Oracle

public void await()
           throws InterruptedException

ทำให้เธรดปัจจุบันรอจนกว่าสลักจะนับถอยหลังเป็นศูนย์เว้นแต่เธรดจะหยุดชะงัก

อ้างถึงคำถาม SE ที่เกี่ยวข้องสำหรับตัวเลือกอื่น ๆ :

รอจนกว่าเธรดทั้งหมดจะทำงานใน java จนเสร็จสิ้น


8

หลีกเลี่ยงคลาสเธรดทั้งหมดและใช้ abstractions ที่สูงกว่าที่มีให้ใน java.util.concurrent แทน

คลาส ExecutorService จัดเตรียมเมธอด invokeAllที่ดูเหมือนจะทำในสิ่งที่คุณต้องการ


6

ลองใช้java.util.concurrent.CountDownLatch. ตัวอย่างในJavadocs


เป็นสลักสำหรับเธรดล็อคสลักทำงานร่วมกับการนับถอยหลัง ในเมธอด run () ของเธรดของคุณประกาศอย่างชัดเจนว่ารอให้ CountDownLatch ถึงมันนับถอยหลังถึง 0 คุณสามารถใช้ CountDownLatch เดียวกันในเธรดมากกว่าหนึ่งเธรดเพื่อปล่อยพร้อมกัน ฉันไม่รู้ว่าเป็นสิ่งที่คุณต้องการหรือเปล่าแค่อยากจะพูดถึงเพราะมันมีประโยชน์เมื่อทำงานในสภาพแวดล้อมแบบมัลติเธรด
Pablo Cavalieri

บางทีคุณควรใส่คำอธิบายนั้นไว้ในเนื้อหาของคำตอบของคุณ?
Aaron Hall

ตัวอย่างใน Javadoc มีคำอธิบายมากนั่นเป็นเหตุผลที่ฉันไม่ได้เพิ่มอะไรเลย docs.oracle.com/javase/7/docs/api/java/util/concurrent/… . ในตัวอย่างแรกเธรด Workers ทั้งหมดถูกรีลีสพร้อมกันเนื่องจากรอให้ CountdownLatch startSignal ถึงศูนย์ซึ่งจะเกิดขึ้นใน startSignal.countDown () จากนั้นเธรด mian จะรอจนกว่างานทั้งหมดจะเสร็จสิ้นโดยใช้คำสั่ง doneSignal.await () doneSignal ลดค่าในแต่ละคน
Pablo Cavalieri

6

ตามที่ Martin K แนะนำ java.util.concurrent.CountDownLatchดูเหมือนจะเป็นทางออกที่ดีกว่าสำหรับเรื่องนี้ เพียงแค่เพิ่มตัวอย่างสำหรับสิ่งเดียวกัน

     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

คุณอาจต้องการตรวจสอบคลาส CountDownLatch และ CyclicBarrier ในแพ็คเกจ java.util.concurrent ทั้งนี้ขึ้นอยู่กับความต้องการของคุณ ซึ่งจะมีประโยชน์หากคุณต้องการให้เธรดของคุณรอกันและกันหรือหากคุณต้องการการควบคุมที่ละเอียดยิ่งขึ้นเกี่ยวกับวิธีดำเนินการเธรดของคุณ (เช่นรอในการดำเนินการภายในเพื่อให้เธรดอื่นตั้งสถานะบางอย่าง) คุณยังสามารถใช้ CountDownLatch เพื่อส่งสัญญาณให้เธรดทั้งหมดของคุณเริ่มต้นพร้อมกันแทนที่จะเริ่มทีละรายการในขณะที่คุณวนซ้ำไปมา เอกสาร API มาตรฐานมีตัวอย่างของสิ่งนี้และการใช้ CountDownLatch อื่นเพื่อรอให้เธรดทั้งหมดดำเนินการให้เสร็จสิ้น


3

หากคุณสร้างรายการของเธรดคุณสามารถวนซ้ำและ .join () กับแต่ละเธรดและลูปของคุณจะสิ้นสุดเมื่อเธรดทั้งหมดมี ฉันยังไม่ได้ลอง

http://docs.oracle.com/javase/8/docs/api/java/lang/Thread.html#join ()


สวัสดีมันไม่ได้ผลสำหรับฉันด้วยเหตุผลบางประการ นี่คือคำถามของฉัน: stackoverflow.com/users/5144855/ruchir-baronia
Ruchir Baronia

1

สร้างวัตถุเธรดภายในตัวแรกสำหรับลูป

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

แล้วสิ่งที่ทุกคนที่นี่พูด

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

0

คุณสามารถทำได้ด้วย Object "ThreadGroup" และพารามิเตอร์ activeCount :


ไม่แน่ใจว่าคุณเสนอให้ทำอย่างไร หากคุณเสนอให้ทำแบบสำรวจ activeCount แบบวนซ้ำนั่นไม่ดีเนื่องจากไม่ว่างรอ (แม้ว่าคุณจะนอนระหว่างการสำรวจความคิดเห็นคุณจะได้รับการแลกเปลี่ยนระหว่างธุรกิจและการตอบสนอง)
Martin v. Löwis

@Martin v. Löwis: "การเข้าร่วมจะรอเพียงเธรดเดียวทางออกที่ดีกว่าอาจเป็น java.util.concurrent.CountDownLatch เพียงแค่เริ่มต้นสลักด้วยการนับที่กำหนดเป็นจำนวนเธรดของผู้ปฏิบัติงานแต่ละเธรดของผู้ปฏิบัติงานควรเรียก countDown () ก่อนที่จะออกและเธรดหลักเรียกว่า await () ซึ่งจะบล็อกจนกว่าตัวนับจะถึงศูนย์ปัญหาของ join () คือคุณไม่สามารถเริ่มเพิ่มเธรดเพิ่มเติมแบบไดนามิกได้รายการจะระเบิด ด้วยการปรับเปลี่ยนพร้อมกัน " โซลูชันของคุณทำงานได้ดีสำหรับปัญหา แต่ไม่ใช่เพื่อวัตถุประสงค์ทั่วไป
Martin K.

0

เป็นทางเลือกให้กับCountDownLatchคุณยังสามารถใช้CyclicBarrierเช่น

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

ในการใช้ CyclicBarrier ในกรณีนี้ barrier.await () ควรเป็นคำสั่งสุดท้ายคือเมื่อเธรดของคุณทำงานเสร็จแล้ว CyclicBarrier สามารถใช้ได้อีกครั้งด้วยวิธีการรีเซ็ต () หากต้องการอ้าง javadocs:

CyclicBarrier สนับสนุนคำสั่ง Runnable ที่เป็นทางเลือกซึ่งรันหนึ่งครั้งต่อจุดกั้นหลังจากที่เธรดสุดท้ายในปาร์ตี้มาถึง แต่ก่อนที่เธรดใด ๆ จะถูกปล่อย การดำเนินการกั้นนี้มีประโยชน์สำหรับการอัปเดตสถานะที่ใช้ร่วมกันก่อนที่ฝ่ายใดฝ่ายหนึ่งจะดำเนินการต่อ


ฉันไม่คิดว่านั่นเป็นตัวอย่างที่ดีสำหรับ CyclicBarrier ทำไมคุณถึงใช้การโทร Thread.sleep ()?
Guenther

@Guenther - ใช่ฉันเปลี่ยนรหัสเพื่อให้ตรงกับความต้องการ
shailendra1118

CyclicBarrier ไม่ใช่ทางเลือกอื่นสำหรับ CountDownLatch เมื่อเธรดต้องนับถอยหลังซ้ำ ๆ คุณควรสร้าง CyclicBarrier มิฉะนั้นจะตั้งค่าเริ่มต้นเป็น CountDownLatch (เว้นแต่จะต้องมีการแยกส่วนการดำเนินการเพิ่มเติมเป็นอย่างอื่นซึ่งคุณควรมองไปที่ระดับที่สูงกว่าคือบริการ)
Elysiumplain

0

join()ไม่เป็นประโยชน์กับผม ดูตัวอย่างนี้ใน Kotlin:

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

ผลลัพธ์:

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

ตอนนี้ให้ฉันใช้join()สำหรับเธรด:

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

และผลลัพธ์:

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

อย่างที่ชัดเจนเมื่อเราใช้join:

  1. เธรดกำลังทำงานตามลำดับ
  2. ตัวอย่างแรกใช้เวลา 765 มิลลิวินาทีในขณะที่ตัวอย่างที่สองใช้เวลา 3833 มิลลิวินาที

ทางออกของเราในการป้องกันการบล็อกเธรดอื่นคือการสร้าง ArrayList:

val threads = ArrayList<Thread>()

ตอนนี้เมื่อเราต้องการเริ่มเธรดใหม่เรามักจะเพิ่มเข้าไปใน ArrayList:

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

addThreadToArrayฟังก์ชั่น:

@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
}

ตรวจสอบความสมบูรณ์ของเธรดด้านล่างทุกที่ที่จำเป็น:

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.
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.