繁体   English   中英

如何等待多个线程完成?

[英]How to wait for a number of threads to complete?

什么是简单地等待所有线程进程完成的方法? 例如,假设我有:

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()方法退出? 谢谢!

你把所有线程放在一个数组中,启动它们,然后有一个循环

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

每个连接都会阻塞,直到相应的线程完成。 线程的完成顺序可能与您加入它们的顺序不同,但这不是问题:当循环退出时,所有线程都已完成。

一种方法是创建一个Thread List ,创建并启动每个线程,同时将其添加到列表中。 启动所有内容后,循环返回列表并在每个列表上调用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并发实用程序中的所有新内容)非常灵活,上面的示例几乎没有触及表面。

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

您可以使用CountDownLatch代替旧 API 的join() 我已将您的代码修改如下以满足您的要求。

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已根据您的要求使用给定的计数 1000 进行初始化。

  2. 每个工作线程DoSomethingInAThread将递减CountDownLatch ,它已在构造函数中传递。

  3. 主线程CountDownLatchDemo await()直到计数变为零。 一旦计数变为零,您将在输出中获得以下行。

     In Main thread after completion of 1000 threads

来自 oracle 文档页面的更多信息

public void await()
           throws InterruptedException

使当前线程等待直到闩锁倒计时为零,除非线程被中断。

有关其他选项,请参阅相关的 SE 问题:

等到所有线程在 java 中完成它们的工作

完全避免使用 Thread 类,而是使用 java.util.concurrent 中提供的更高抽象

ExecutorService 类提供的方法 invokeAll似乎可以满足您的需求。

考虑使用java.util.concurrent.CountDownLatch javadoc 中的示例

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

}

根据您的需要,您可能还想查看 java.util.concurrent 包中的 CountDownLatch 和 CyclicBarrier 类。 如果您希望线程相互等待,或者希望对线程执行方式进行更细粒度的控制(例如,在它们的内部执行中等待另一个线程设置某些状态),它们会很有用。 您还可以使用 CountDownLatch 来通知所有线程同时启动,而不是在循环遍历时一个一个地启动它们。 标准 API 文档有一个这样的例子,另外还使用另一个 CountDownLatch 来等待所有线程完成它们的执行。

如果您列出线程,您可以遍历它们并对每个线程执行 .join() ,当所有线程都完成时,您的循环将结束。 不过我没试过。

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

这将是一条评论,但我还不能发表评论。

Martin K ,我很好奇您如何使用ThreadGroup 你以前做过吗?

我看到上面的,你建议检查activeCount -除了撇开马丁 五世 Löwis的关切投票的那一刻,我有另一个值得关注的activeCount本身。

警告:我还没有尝试使用它,所以我对此不是专家,但是根据javadocs ,它返回活动线程数的估计值

就我个人而言,我不愿意尝试在估计的基础上建立一个系统。 您是否还有其他想法,还是我误读了Javadoc?

在第一个 for 循环内创建线程对象。

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

作为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 可以通过其 reset() 方法再次使用。 引用 javadocs:

CyclicBarrier 支持可选的 Runnable 命令,该命令在每个屏障点运行一次,在派对中的最后一个线程到达之后,但在任何线程被释放之前。 此屏障操作对于在任何一方继续之前更新共享状态很有用。

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

startNewThread函数:

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

问题在于:

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

...是, threads[i + 1]永远不能在threads[i]之前加入。 除了“闩锁”的那些,所有的解决方案都有这个缺陷。

这里(还)没有人提到ExecutorCompletionService ,它允许根据完成顺序加入线程/任务:

public class ExecutorCompletionService<V> extends Object implements CompletionService<V>

CompletionService使用提供的Executor来执行任务。 此类安排提交的任务在完成后放置在使用 take 可访问的队列中。 该类足够轻量级,适合在处理任务组时临时使用。

用法示例。

假设您有一组针对某个问题的求解器,每个求解器都返回某种 Result 类型的值,并且希望并发运行它们,处理它们中每个返回非空值的结果,在某些方法中use(Result r) 你可以这样写:

 void solve(Executor e, Collection<Callable<Result>> solvers) throws InterruptedException, ExecutionException { CompletionService<Result> cs = new ExecutorCompletionService<>(e); solvers.forEach(cs::submit); for (int i = solvers.size(); i > 0; i--) { Result r = cs.take().get(); if (r != null) use(r); } }

假设您想使用任务集的第一个非空结果,忽略任何遇到异常的结果,并在第一个任务准备好时取消所有其他任务:

 void solve(Executor e, Collection<Callable<Result>> solvers) throws InterruptedException { CompletionService<Result> cs = new ExecutorCompletionService<>(e); int n = solvers.size(); List<Future<Result>> futures = new ArrayList<>(n); Result result = null; try { solvers.forEach(solver -> futures.add(cs.submit(solver))); for (int i = n; i > 0; i--) { try { Result r = cs.take().get(); if (r != null) { result = r; break; } } catch (ExecutionException ignore) {} } } finally { futures.forEach(future -> future.cancel(true)); } if (result != null) use(result); }

从:1.5 (!)

假设use(r) (例 1 的)也是异步的,我们有很大的优势。 #

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM