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


94

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


4
Thread.joinเป็นวิธีการแก้ปัญหาเฉพาะ Java ในระดับต่ำ นอกจากนี้ยังเป็นปัญหาเนื่องจากThread API มีข้อบกพร่อง: คุณไม่สามารถทราบได้ว่าการเข้าร่วมเสร็จสมบูรณ์หรือไม่ (ดูJava Concurrency In Practice ) สิ่งที่เป็นนามธรรมในระดับที่สูงขึ้นเช่นการใช้CountDownLatchอาจเป็นที่ต้องการและจะดูเป็นธรรมชาติมากขึ้นสำหรับโปรแกรมเมอร์ที่ไม่ได้ "ติดอยู่" ในความคิดแบบ Java-idiosynchratic อย่าเถียงฉันไปเถียง Doug Lea; )
Cedric Martin

คำตอบ:


122

แนวทางที่ฉันใช้คือการใช้ExecutorServiceเพื่อจัดการกลุ่มของเธรด

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 () ทำ
Peter Lawrey

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

3
@AquariusPower คุณสามารถบอกให้รออีกต่อไปหรือตลอดไป
Peter Lawrey

1
อ้อ! ฉันเข้าใจแล้ว; ดังนั้นฉันจึงเพิ่มข้อความในลูปว่ากำลังรอให้เธรดทั้งหมดเสร็จสิ้น ขอบคุณ!
Aquarius Power

1
@PeterLawrey จำเป็นไหมที่ต้องโทรes.shutdown();? จะเกิดอะไรขึ้นถ้าฉันเขียนโค้ดที่ฉันเรียกใช้เธรดโดยใช้es.execute(runnableObj_ZipMaking);ในtryบล็อกและfinallyฉันเรียกว่าboolean finshed = es.awaitTermination(10, TimeUnit.MINUTES);. ดังนั้นฉันคิดว่าสิ่งนี้ควรรอจนกว่าเธรดทั้งหมดจะทำงานเสร็จหรือหมดเวลาเกิดขึ้น (อะไรก็ตามที่เกิดก่อน) สมมติฐานของฉันถูกต้องหรือไม่? หรือโทรไปshutdown()เป็นภาคบังคับ?
Amogh

54

คุณสามารถjoinไปที่เธรด เข้าร่วมบล็อกจนกว่าเธรดจะเสร็จสมบูรณ์

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

โปรดทราบว่าการjoinพ่นInterruptedExceptionไฟล์. คุณจะต้องตัดสินใจว่าจะทำอย่างไรหากเกิดเหตุการณ์เช่นนี้ (เช่นพยายามยกเลิกเธรดอื่น ๆ เพื่อป้องกันไม่ให้งานที่ไม่จำเป็นต้องทำ)


1
เธรดเหล่านี้ทำงานแบบขนานหรือตามลำดับซึ่งกันและกัน?
James Webster

5
@JamesWebster: ขนาน
RYN

4
@ James Webster: คำสั่งt.join();หมายความว่าเธรดปัจจุบันบล็อกจนกว่าเธรดtจะสิ้นสุด tมันไม่ได้ส่งผลกระทบต่อด้าย
Mark Byers

1
ขอบคุณ. =] ศึกษาเรื่อง Paralellism ที่มหาวิทยาลัย แต่นั่นเป็นสิ่งเดียวที่ฉันพยายามเรียนรู้! โชคดีที่ฉันไม่ต้องใช้มันมากในตอนนี้หรือเมื่อฉันทำมันก็ไม่ซับซ้อนเกินไปหรือไม่มีทรัพยากรที่ใช้ร่วมกันและการบล็อกก็ไม่สำคัญ
James Webster

1
@ 4r1y4n โค้ดที่ให้มาจะขนานกันจริงหรือไม่นั้นขึ้นอยู่กับสิ่งที่คุณกำลังพยายามทำอยู่และเกี่ยวข้องกับการรวบรวมข้อมูลที่กระจายไปทั่วคอลเลกชันโดยใช้เธรดที่เข้าร่วม คุณกำลังเข้าร่วมชุดข้อความซึ่งอาจหมายถึงข้อมูล "การเข้าร่วม" นอกจากนี้ความเท่าเทียมกันไม่จำเป็นต้องหมายถึงการเกิดพร้อมกัน ขึ้นอยู่กับซีพียู อาจเป็นไปได้ว่าเธรดกำลังทำงานแบบขนาน แต่การคำนวณจะเกิดขึ้นตามลำดับใดก็ตามที่กำหนดโดย CPU ที่อยู่เบื้องหลัง

22

ลองดูวิธีแก้ปัญหาต่างๆ

  1. join()API ได้รับการแนะนำใน Java เวอร์ชันแรก ๆ ทางเลือกที่ดีบางอย่างมีให้ในแพ็คเกจพร้อมกันนี้ตั้งแต่รุ่น JDK 1.5

  2. ExecutorService # invokeAll ()

    ดำเนินงานที่กำหนดส่งคืนรายการ Futures ที่มีสถานะและผลลัพธ์เมื่อทุกอย่างเสร็จสมบูรณ์

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

    จะใช้ invokeAll () เพื่อให้เธรดพูลทั้งหมดทำงานได้อย่างไร

  3. CountDownLatch

    ตัวช่วยในการซิงโครไนซ์ที่อนุญาตให้เธรดอย่างน้อยหนึ่งเธรดรอจนกว่าชุดของการดำเนินการในเธรดอื่นจะเสร็จสิ้น

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

    อ้างถึงคำถามนี้สำหรับการใช้งาน CountDownLatch

    จะรอด้ายที่เกิดด้ายของตัวเองได้อย่างไร?

  4. ForkJoinPoolหรือ newWorkStealingPool ()ในExecutors

  5. วนซ้ำผ่านวัตถุในอนาคตทั้งหมดที่สร้างขึ้นหลังจากส่งไปที่ExecutorService


11

นอกเหนือจากที่Thread.join()คนอื่นแนะนำแล้ว java 5 ยังแนะนำกรอบตัวดำเนินการ คุณไม่ได้ทำงานกับThreadวัตถุ คุณส่งCallableหรือRunnableวัตถุของคุณไปยังผู้ปฏิบัติการแทน มีตัวดำเนินการพิเศษที่มีไว้เพื่อดำเนินการหลาย ๆ งานและส่งคืนผลลัพธ์ที่ไม่เป็นระเบียบ นั่นคือExecutorCompletionService:

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

จากนั้นคุณสามารถโทรซ้ำ ๆ ได้take()จนกว่าจะไม่มีFuture<?>วัตถุให้ส่งคืนซึ่งหมายความว่าทั้งหมดจะเสร็จสมบูรณ์


CyclicBarrierสิ่งที่อาจเกี่ยวข้องอีกขึ้นอยู่กับสถานการณ์ของคุณคือ

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


ใกล้เสร็จแล้ว แต่ฉันยังคงทำการปรับเปลี่ยนสองสามอย่าง executor.submitส่งคืน a Future<?>. ฉันจะเพิ่มฟิวเจอร์สเหล่านี้ลงในรายการแล้ววนซ้ำรายการที่เรียกร้องgetในอนาคตแต่ละครั้ง
Ray

นอกจากนี้คุณสามารถสร้างอินสแตนซ์คอนสตรัคเตอร์โดยใช้Executorsเช่นExecutors.newCachedThreadPool(หรือคล้ายกัน)
Ray

10

ความเป็นไปได้อีกประการหนึ่งคือCountDownLatchวัตถุซึ่งมีประโยชน์สำหรับสถานการณ์ง่ายๆเนื่องจากคุณทราบจำนวนเธรดล่วงหน้าคุณจึงเริ่มต้นด้วยจำนวนที่เกี่ยวข้องและส่งการอ้างอิงของวัตถุไปยังแต่ละเธรด
เมื่อเสร็จสิ้นภารกิจแต่ละเธรดจะเรียกใช้CountDownLatch.countDown()ซึ่งจะลดตัวนับภายใน เธรดหลักหลังจากเริ่มต้นอื่น ๆ ทั้งหมดแล้วควรทำการCountDownLatch.await()บล็อกการโทร มันจะออกทันทีที่ตัวนับภายในถึง 0

ให้ความสนใจว่าด้วยวัตถุนี้InterruptedExceptionสามารถโยนได้เช่นกัน


8

รอ / บล็อกเธรดเมนจนกว่าเธรดอื่นจะทำงานเสร็จ

ในฐานะที่@Ravindra babuบอกว่ามันสามารถทำได้ในรูปแบบต่างๆ แต่การแสดงที่มีตัวอย่าง

  • java.lang.Thread. เข้าร่วม () ตั้งแต่: 1.0.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 ตั้งแต่: 1.5

    • .countDown() «ลดจำนวนของกลุ่มสลัก
    • .await() «วิธีการรอจะบล็อกจนกว่าจำนวนปัจจุบันจะถึงศูนย์

    หากคุณสร้างlatchGroupCount = 4แล้วcountDown()ควรเรียก 4 ครั้งเพื่อนับ 0 ดังนั้นawait()จะเป็นการคลายเธรดที่บล็อก

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

LatchTaskรหัสตัวอย่างของการเรียนการตอบ เพื่อทดสอบการใช้แนวทางjoiningThreads(); และlatchThreads();จากวิธีการหลัก

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ตัวช่วยในการซิงโครไนซ์ที่ช่วยให้ชุดของเธรดทั้งหมดรอให้กันและกันไปถึงจุดกั้นร่วมกัน CyclicBarriers มีประโยชน์ในโปรแกรมที่เกี่ยวข้องกับกลุ่มของเธรดขนาดคงที่ซึ่งต้องรอกันเป็นครั้งคราว สิ่งกีดขวางนี้เรียกว่าไซคลิกเนื่องจากสามารถใช้ซ้ำได้หลังจากปล่อยเธรดที่รอแล้ว
    CyclicBarrier barrier = new CyclicBarrier(3);
    barrier.await();
    
    ตัวอย่างเช่นอ้างถึงคลาสConcurrent_ParallelNotifyies

  • เฟรมเวิร์ก Executer: เราสามารถใช้ExecutorServiceเพื่อสร้างเธรดพูลและติดตามความคืบหน้าของงานอะซิงโครนัสกับอนาคต

    • submit(Runnable), submit(Callable)ซึ่งวัตถุผลตอบแทนในอนาคต ด้วยการใช้future.get()ฟังก์ชันเราสามารถบล็อกเธรดหลักได้จนกว่าเธรดที่ทำงานจะทำงานเสร็จสิ้น

    • invokeAll(...) - ส่งคืนรายการวัตถุในอนาคตซึ่งคุณสามารถรับผลลัพธ์ของการดำเนินการของแต่ละ Callable

ค้นหาตัวอย่างการใช้ Interfaces Runnable, Callable with Executor framework


@ดูสิ่งนี้ด้วย


7

คุณทำ

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

หลังจากนี้สำหรับการวนซ้ำคุณสามารถมั่นใจได้ว่าเธรดทั้งหมดทำงานเสร็จแล้ว


4

เก็บเธรดอ็อบเจ็กต์ไว้ในคอลเลกชันบางส่วน (เช่นรายการหรือชุด) จากนั้นวนซ้ำคอลเลกชันเมื่อเธรดเริ่มต้นและเรียกใช้join ()บนเธรด



2

แม้ว่าจะไม่เกี่ยวข้องกับปัญหาของ OP แต่หากคุณสนใจในการซิงโครไนซ์ (อย่างแม่นยำมากขึ้นการนัดพบ) กับเธรดเดียวคุณอาจใช้Exchanger

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


1

บริการตัวดำเนินการสามารถใช้เพื่อจัดการเธรดหลายชุดรวมถึงสถานะและความสมบูรณ์ ดูhttp://programmingexamples.wikidot.com/executorservice


1

ลองนี้จะได้ผล

  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

ฉันมีปัญหาที่คล้ายกันและลงเอยด้วยการใช้ Java 8 parallelStream

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

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

ข้อมูลเพิ่มเติมเกี่ยวกับขนานลำธารที่นี่


1

ฉันสร้างวิธีการช่วยเหลือขนาดเล็กเพื่อรอไม่กี่เธรดเสร็จสิ้น:

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

0

คำตอบที่มีอยู่กล่าวได้join()ในแต่ละหัวข้อ

แต่มีหลายวิธีในการรับเธรดอาร์เรย์ / รายการ:

  • เพิ่มเธรดลงในรายการที่สร้าง
  • ใช้ThreadGroupเพื่อจัดการเธรด

รหัสต่อไปนี้จะใช้ThreadGruopแนวทาง สร้างกลุ่มก่อนจากนั้นเมื่อสร้างแต่ละเธรดให้ระบุกลุ่มในตัวสร้างหลังจากนั้นจะได้รับเธรดอาร์เรย์ผ่านThreadGroup.enumerate()


รหัส

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

เธรดหลักจะรอให้เธรดทั้งหมดในกลุ่มเสร็จสิ้น


-1

ใช้สิ่งนี้ในเธรดหลักของคุณ: while (! executor.isTerminated ()); ใส่โค้ดบรรทัดนี้หลังจากเริ่มเธรดทั้งหมดจากบริการเรียกใช้งาน การดำเนินการนี้จะเริ่มต้นเธรดหลักหลังจากเธรดทั้งหมดที่เริ่มโดยตัวดำเนินการเสร็จสิ้น ตรวจสอบให้แน่ใจว่าได้เรียกใช้ executor.shutdown (); ก่อนลูปด้านบน


นี่คือการรอที่ใช้งานอยู่ซึ่งจะทำให้ CPU ทำงานวนรอบว่างอยู่ตลอดเวลา สิ้นเปลืองมาก.
Adam Michalik
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.