等到所有线程在Java中完成工作


91

我正在编写一个具有5个线程的应用程序,这些线程可以同时从Web获取一些信息,并在缓冲区类中填充5个不同的字段。
当所有线程完成其工作时,我需要验证缓冲区数据并将其存储在数据库中。
我该怎么做(当所有线程完成工作时收到警报)?


4
Thread.join是一种相当底层的非常Java特有的解决问题的方法。此外,因为它是有问题的线程 API是有缺陷的:你可以不知道是否加入 successfuly或没有完成(见Java并发实践)。较高级别的抽象(例如使用CountDownLatch)可能是更可取的,并且对于那些不会“陷入” Java特有思维定式的程序员来说,它们看起来更加自然。不要跟我争论,要和道格·李争论;)
塞德里克·马丁,

Answers:


119

我采用的方法是使用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()所做的。
彼得·劳瑞

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

3
@AquariusPower您可以告诉它等待更长或更长时间。
彼得·劳瑞

1
哦..我明白了;所以我在循环中添加了一条消息,说它正在等待所有线程完成;谢谢!
Aquarius Power

1
@PeterLawrey,有必要打电话es.shutdown();吗?如果我编写了代码,并在其中使用es.execute(runnableObj_ZipMaking);in try块执行了一个线程,然后finally调用了该怎么办boolean finshed = es.awaitTermination(10, TimeUnit.MINUTES);?因此,我认为这应该等到所有线程完成工作或发生超时(无论哪个优先)后,我的假设是正确的吗?或致电shutdown()是强制性的?
Amogh

52

您可以join到线程。连接阻塞直到线程完成。

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

请注意,它join会引发一个InterruptedException。您必须决定如果发生这种情况该怎么办(例如,尝试取消其他线程以防止不必要的工作完成)。


1
这些线程是并行运行还是彼此顺序运行?
詹姆斯·韦伯斯特

5
@JamesWebster:平行。
RYN 2011年

4
@James Webster:该语句t.join();表示当前线程一直阻塞直到线程t终止。它不影响线程t
马克·拜尔斯

1
谢谢。=]在uni学习过平行论,但这是我努力学习的唯一内容!值得庆幸的是,我不必现在就使用它,也不必太复杂或没有共享资源并且阻塞也不是很重要
James Webster

1
@ 4r1y4n所提供的代码是否真正并行取决于您要使用的代码,并且与使用联接线程聚合分布在整个集合中的数据有关。您正在加入线程,这可能意味着“加入”数据。同样,并行性并不一定意味着并发。这取决于CPU。线程很可能并行运行,但是计算以底层CPU确定的顺序进行。

22

看看各种解决方案。

  1. join()API已在Java的早期版本中引入。自JDK 1.5发行以来,此发包提供了一些不错的选择。

  2. ExecutorService#invokeAll()

    执行给定的任务,并在完成所有操作后返回持有其状态和结果的期货列表。

    请参考此相关的SE问题以获取代码示例:

    如何使用invokeAll()让所有线程池执行任务?

  3. CountDownLatch

    一种同步帮助,它允许一个或多个线程等待,直到在其他线程中执行的一组操作完成为止。

    一个CountDownLatch初始化为给定数。由于该countDown()方法的调用,直到当前计数达到零为止,await方法将阻塞,此后所有释放的线程将被释放,并且任何随后的await调用将立即返回。这是一种一次性现象-无法重置计数。如果需要用于重置计数的版本,请考虑使用CyclicBarrier

    请参阅此问题以了解 CountDownLatch

    如何等待产生自己线程的线程?

  4. ForkJoinPoolnewWorkStealingPool()执行人

  5. 遍历提交给以下对象后创建的所有Future对象ExecutorService


11

除了Thread.join()其他建议外,java 5引入了执行程序框架。在那里,您不使用Thread对象。相反,您将CallableRunnable对象提交给执行者。有一个特殊的执行程序,旨在执行多个任务并按顺序返回其结果。那是ExecutorCompletionService

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

然后,您可以重复调用,take()直到没有其他Future<?>要返回的对象为止,这意味着所有对象都已完成。


根据您的情况,另一件事可能是相关的CyclicBarrier

一种同步辅助工具,它允许一组线程全部互相等待以到达一个公共的障碍点。CyclicBarriers在涉及固定大小的线程方的程序中很有用,该线程方有时必须互相等待。屏障被称为循环屏障,因为它可以在释放等待线程之后重新使用。


这已经很接近了,但是我仍然要进行一些调整。executor.submit返回Future<?>。我会将这些期货添加到列表中,然后遍历该列表并调用get每个期货。

另外,您可以使用实例化构造函数Executors,例如Executors.newCachedThreadPool(或类似的)
Ray,

10

CountDownLatch对象的另一种可能性是,它在简单情况下很有用:由于您事先知道线程数,因此请使用相关计数对其进行初始化,然后将该对象的引用传递给每个线程。
完成任务后,每个线程都会调用CountDownLatch.countDown(),这会减少内部计数器的数量。在启动所有其他线程之后,主线程应执行CountDownLatch.await()阻塞调用。一旦内部计数器达到0,它将被释放。

请注意,使用此对象,InterruptedException也可能引发。


8

你做

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

在此for循环之后,您可以确保所有线程都已完成其工作。


6

等待/阻塞线程主线程,直到其他一些线程完成其工作。

如前所述@Ravindra babu,可以通过各种方式来实现,但要举例说明。

  • java.lang.Thread。join() 从: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 :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.
    }

Threaded类的示例代码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类。

  • 执行器框架:我们可以使用ExecutorService创建线程池,并使用Future跟踪异步任务的进度。

    • submit(Runnable)submit(Callable)它返回Future对象。通过使用future.get()函数,我们可以阻塞主线程,直到工作线程完成其工作为止。

    • invokeAll(...) -返回Future对象的列表,通过它们可以获取每个Callable的执行结果。

查找在Executor框架中使用可运行,可调用接口的示例


@也可以看看


4

将线程对象存储到某个集合(如列表或集合)中,然后在线程启动后遍历该集合并在线程上调用join()



2

尽管与OP的问题无关,但如果您对仅使用一个线程进行同步(更确切地说,是集合点)感兴趣,则可以使用一个 Exchanger

就我而言,我需要暂停父线程,直到子线程执行某些操作,例如完成其初始化。一个CountDownLatch也是行之有效的。



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的fork连接池,这意味着它将等待所有线程完成后再继续。就我而言,这是一个很好的解决方案,因为它是我的应用程序中唯一的parallelStream。如果您同时运行多个parallelStream,请阅读下面的链接。

有关并行流的更多信息,请参见此处


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

主线程将等待组中的所有线程完成。


0

我创建了一个小的帮助程序方法,以等待一些线程完成:

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

-1

在您的主线程中使用它:while(!executor.isTerminated()); 从执行程序服务启动所有线程后,放置此行代码。这只会在执行程序启动的所有线程完成后才启动主线程。确保调用executor.shutdown(); 在上述循环之前。


这是活动等待,这将导致CPU不断运行空循环。非常浪费。
Adam Michalik
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.