如何知道其他线程是不是已经完成?

Posted

技术标签:

【中文标题】如何知道其他线程是不是已经完成?【英文标题】:How to know if other threads have finished?如何知道其他线程是否已经完成? 【发布时间】:2010-10-16 16:32:04 【问题描述】:

我有一个对象,其方法名为 StartDownload(),它启动三个线程。

如何在每个线程完成执行时收到通知?

有没有办法知道一个(或全部)线程是否已完成或仍在执行?

【问题讨论】:

看看Java 5 Barrier class 【参考方案1】:

我想最简单的方法是使用ThreadPoolExecutor 类。

    它有一个队列,您可以设置并行工作的线程数。 它有很好的回调方法:

挂钩方法

此类提供受保护的可重写beforeExecute(java.lang.Thread, java.lang.Runnable)afterExecute(java.lang.Runnable, java.lang.Throwable) 方法,这些方法在执行每个任务之前和之后调用。这些可用于操纵执行环境;例如,重新初始化 ThreadLocals、收集统计信息或添加日志条目。此外,可以覆盖方法terminated() 以执行任何需要在 Executor 完全终止后完成的特殊处理。

这正是我们所需要的。我们将覆盖afterExecute() 以在每个线程完成后获取回调,并将覆盖terminated() 以了解所有线程何时完成。

这就是你应该做的事情

    创建一个执行器:

    private ThreadPoolExecutor executor;
    private int NUMBER_OF_CORES = Runtime.getRuntime().availableProcessors();    
    
    
    
    private void initExecutor() 
    
    executor = new ThreadPoolExecutor(
            NUMBER_OF_CORES * 2,  //core pool size
            NUMBER_OF_CORES * 2, //max pool size
            60L, //keep aive time
            TimeUnit.SECONDS,
            new LinkedBlockingQueue<Runnable>()
    ) 
    
        @Override
        protected void afterExecute(Runnable r, Throwable t) 
            super.afterExecute(r, t);
                //Yet another thread is finished:
                informUiAboutProgress(executor.getCompletedTaskCount(), listOfUrisToProcess.size());
            
        
    
    ;
    
        @Override
        protected void terminated() 
            super.terminated();
            informUiThatWeAreDone();
        
    
    
    

    然后开始你的线程:

    private void startTheWork()
        for (Uri uri : listOfUrisToProcess) 
            executor.execute(new Runnable() 
                @Override
                public void run() 
                    doSomeHeavyWork(uri);
                
            );
        
        executor.shutdown(); //call it when you won't add jobs anymore 
    
    

内部方法informUiThatWeAreDone();在所有线程完成后做任何你需要做的事情,例如,更新UI。

注意:不要忘记使用synchronized 方法,因为您并行工作,如果您决定从另一个synchronized 方法调用synchronized 方法,请务必小心!这通常会导致死锁

希望这会有所帮助!

【讨论】:

【参考方案2】:

使用CyclicBarrier的解决方案

public class Downloader 
  private CyclicBarrier barrier;
  private final static int NUMBER_OF_DOWNLOADING_THREADS;

  private DownloadingThread extends Thread 
    private final String url;
    public DownloadingThread(String url) 
      super();
      this.url = url;
    
    @Override
    public void run() 
      barrier.await(); // label1
      download(url);
      barrier.await(); // label2
    
  
  public void startDownload() 
    // plus one for the main thread of execution
    barrier = new CyclicBarrier(NUMBER_OF_DOWNLOADING_THREADS + 1); // label0
    for (int i = 0; i < NUMBER_OF_DOWNLOADING_THREADS; i++) 
      new DownloadingThread("http://www.flickr.com/someUser/pic" + i + ".jpg").start();
    
    barrier.await(); // label3
    displayMessage("Please wait...");
    barrier.await(); // label4
    displayMessage("Finished");
  

label0 - 创建循环屏障,参与方的数量等于执行线程的数量加上主执行线程的数量(其中正在执行 startDownload())

标签1 - 第n个DownloadingThread进入等候室

标签 3 - NUMBER_OF_DOWNLOADING_THREADS 个已进入等候室。执行的主线程释放它们以或多或少同时开始执行下载工作

标签 4 - 执行的主线程进入等候室。这是要理解的代码中“最棘手”的部分。哪个线程第二次进入等候室并不重要。重要的是,无论哪个线程最后进入房间,都要确保所有其他下载线程都完成了他们的下载工作。

标签 2 - 第 n 个 DownloadingThread 已完成下载工作并进入等候室。如果是最后一个,即已经有 NUMBER_OF_DOWNLOADING_THREADS 个进入,包括执行的主线程,只有在其他所有线程都完成下载后,主线程才会继续执行。

【讨论】:

【参考方案3】:

查看 Thread 类的 Java 文档。您可以检查线程的状态。如果把三个线程放在成员变量中,那么三个线程都可以互相读取状态。

不过,您必须小心一点,因为您可能会导致线程之间出现竞争条件。尽量避免基于其他线程状态的复杂逻辑。绝对避免多个线程写入相同的变量。

【讨论】:

【参考方案4】:

这是一个简单、简短、易于理解且非常适合我的解决方案。当另一个线程结束时,我需要绘制到屏幕上;但不能,因为主线程可以控制屏幕。所以:

(1) 我创建了全局变量:boolean end1 = false; 线程在结束时将其设置为 true。这是由“postDelayed”循环在主线程中拾取的,并在其中进行响应。

(2) 我的帖子包含:

void myThread() 
    end1 = false;
    new CountDownTimer(((60000, 1000)  // milliseconds for onFinish, onTick
        public void onFinish()
        
            // do stuff here once at end of time.
            end1 = true; // signal that the thread has ended.
        
        public void onTick(long millisUntilFinished)
        
          // do stuff here repeatedly.
        
    .start();


(3) 幸运的是,“postDelayed”在主线程中运行,因此每秒检查一次另一个线程。当另一个线程结束时,这可以开始我们接下来想做的任何事情。

Handler h1 = new Handler();

private void checkThread() 
   h1.postDelayed(new Runnable() 
      public void run() 
         if (end1)
            // resond to the second thread ending here.
         else
            h1.postDelayed(this, 1000);
      
   , 1000);

(4) 最后,通过调用在代码中某处开始运行整个事情:

void startThread()

   myThread();
   checkThread();

【讨论】:

【参考方案5】:

我建议查看 Thread 类的 javadoc。

您有多种线程操作机制。

您的主线程可以连续join() 三个线程,然后在三个线程都完成之前不会继续。

每隔一段时间轮询派生线程的线程状态。

将所有生成的线程放入一个单独的ThreadGroup 并轮询ThreadGroup 上的activeCount() 并等待它变为0。

为线程间通信设置自定义回调或侦听器类型的接口。

我敢肯定还有很多其他方法我仍然想念。

【讨论】:

【参考方案6】:

在过去 6 年中,多线程方面发生了很多变化。

你可以使用join()和lock API,而不是使用

1.ExecutorServiceinvokeAll()API

执行给定的任务,当所有任务完成时返回一个包含状态和结果的 Futures 列表。

2.CountDownLatch

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

CountDownLatch 使用给定的计数进行初始化。由于调用countDown() 方法,await 方法一直阻塞,直到当前计数达到零,之后所有等待的线程都被释放,任何后续的 await 调用立即返回。这是一次性现象——计数无法重置。如果您需要重置计数的版本,请考虑使用 CyclicBarrier。

3.ForkJoinPool 或 newWorkStealingPool() in Executors 是其他方式

4.从ExecutorService 上的提交遍历所有Future 任务,并通过Future 对象上的阻塞调用get() 检查状态

查看相关的 SE 问题:

How to wait for a thread that spawns it's own thread?

Executors: How to synchronously wait until all tasks have finished if tasks are created recursively?

【讨论】:

【参考方案7】:

您还可以使用Executors 对象来创建ExecutorService 线程池。然后使用invokeAll 方法运行每个线程并检索Futures。这将阻塞,直到所有都完成执行。您的另一种选择是使用池执行每一个,然后调用awaitTermination 进行阻塞,直到池完成执行。添加任务后请务必致电shutdown()。

【讨论】:

【参考方案8】:

您应该真的更喜欢使用java.util.concurrent 的解决方案。查找并阅读有关该主题的 Josh Bloch 和/或 Brian Goetz。

如果您不使用java.util.concurrent.* 并且负责直接使用线程,那么您可能应该使用join() 来了解线程何时完成。这是一个超级简单的回调机制。先扩展Runnable接口,使其有回调:

public interface CallbackRunnable extends Runnable 
    public void callback();

然后创建一个 Executor 来执行你的 runnable 并在完成后给你回电。

public class CallbackExecutor implements Executor 

    @Override
    public void execute(final Runnable r) 
        final Thread runner = new Thread(r);
        runner.start();
        if ( r instanceof CallbackRunnable ) 
            // create a thread to perform the callback
            Thread callerbacker = new Thread(new Runnable() 
                @Override
                public void run() 
                    try 
                        // block until the running thread is done
                        runner.join();
                        ((CallbackRunnable)r).callback();
                    
                    catch ( InterruptedException e ) 
                        // someone doesn't want us running. ok, maybe we give up.
                    
                
            );
            callerbacker.start();
        
    


添加到CallbackRunnable 接口的另一种显而易见的事情是处理任何异常的方法,因此可以在其中放置一个public void uncaughtException(Throwable e); 行并在您的执行程序中安装一个 Thread.UncaughtExceptionHandler 将您发送到那个接口方法。

但是做这一切真的开始闻起来像java.util.concurrent.Callable。如果您的项目允许,您应该考虑使用java.util.concurrent

【讨论】:

我有点不清楚你从这个回调机制中获得了什么,而不是简单地调用runner.join(),然后再调用任何你想要的代码,因为你知道线程已经完成。是否只是您可以将该代码定义为可运行对象的属性,这样您就可以为不同的可运行对象提供不同的东西? 是的,runner.join() 是最直接的等待方式。我假设 OP 不想阻止他们的主调用线程,因为他们要求每次下载都得到“通知”,这可以按任何顺序完成。这提供了一种异步获取通知的方法。【参考方案9】:

您还可以使用具有内置属性更改支持的 SwingWorker。请参阅 addPropertyChangeListener() 或 get() 方法以获取状态更改侦听器示例。

【讨论】:

【参考方案10】:

您可以通过多种方式做到这一点:

    在主线程中使用Thread.join() 以阻塞方式等待每个线程完成,或者 以轮询方式检查Thread.isAlive()(通常不鼓励)等待每个线程完成,或者 非正统的,对于每个有问题的线程,调用setUncaughtExceptionHandler 来调用对象中的一个方法,并对每个线程进行编程以在它完成时抛出一个未捕获的异常,或者 使用来自java.util.concurrent 的锁或同步器或机制,或者 更正统的做法是,在您的主线程中创建一个监听器,然后对您的每个线程进行编程以告诉监听器它们已经完成。

如何实现想法#5?好吧,一种方法是先创建一个接口:

public interface ThreadCompleteListener 
    void notifyOfThreadComplete(final Thread thread);

然后创建以下类:

public abstract class NotifyingThread extends Thread 
  private final Set<ThreadCompleteListener> listeners
                   = new CopyOnWriteArraySet<ThreadCompleteListener>();
  public final void addListener(final ThreadCompleteListener listener) 
    listeners.add(listener);
  
  public final void removeListener(final ThreadCompleteListener listener) 
    listeners.remove(listener);
  
  private final void notifyListeners() 
    for (ThreadCompleteListener listener : listeners) 
      listener.notifyOfThreadComplete(this);
    
  
  @Override
  public final void run() 
    try 
      doRun();
     finally 
      notifyListeners();
    
  
  public abstract void doRun();

然后你的每个线程将扩展NotifyingThread,而不是实现run(),它将实现doRun()。因此,当它们完成时,它们会自动通知任何等待通知的人。

最后,在您的主类中——启动所有线程(或至少是等待通知的对象)的主类——将该类修改为implement ThreadCompleteListener,并在创建每个线程后立即将自身添加到侦听器列表中:

NotifyingThread thread1 = new OneOfYourThreads();
thread1.addListener(this); // add ourselves as a listener
thread1.start();           // Start the Thread

然后,当每个 Thread 退出时,您的 notifyOfThreadComplete 方法将与刚刚完成(或崩溃)的 Thread 实例一起调用。

请注意,对于NotifyingThread,最好使用implements Runnable 而不是extends Thread,因为在新代码中通常不鼓励扩展线程。但我正在为你的问题编码。如果您将NotifyingThread 类更改为实现Runnable,那么您必须更改一些管理线程的代码,这很容易做到。

【讨论】:

但是使用这种方法,notifiyListeners 是在 run() 内部调用的,所以它会在线程内部被调用,进一步的调用也会在那里完成,不是这样吗? @Jordi Puigdellivol:我不明白你的问题。 @Eddie Jordi 在问,是否可以调用 notify 方法,而不是在 run 方法中,而是在它之后。 真正的问题是:你现在如何关闭次线程。我知道它已经完成了,但是我现在如何访问 Main 线程? 这个线程安全吗?似乎 notifyListeners(以及因此 notifyOfThreadComplete)将在 NotifyingThread 内调用,而不是在创建监听器本身的线程内调用。【参考方案11】:

您可以使用 getState() 查询线程实例,它返回具有以下值之一的 Thread.State 枚举实例:

*  NEW
  A thread that has not yet started is in this state.
* RUNNABLE
  A thread executing in the Java virtual machine is in this state.
* BLOCKED
  A thread that is blocked waiting for a monitor lock is in this state.
* WAITING
  A thread that is waiting indefinitely for another thread to perform a particular action is in this state.
* TIMED_WAITING
  A thread that is waiting for another thread to perform an action for up to a specified waiting time is in this state.
* TERMINATED
  A thread that has exited is in this state.

但是我认为拥有一个等待 3 个子进程完成的主线程会是一个更好的设计,当其他 3 个子进程完成时,主线程会继续执行。

【讨论】:

等待 3 个孩子退出可能不适合使用范式。如果这是一个下载管理器,他们可能希望开始 15 次下载并简单地从状态栏中删除状态或在下载完成时提醒用户,在这种情况下回调会更好。【参考方案12】:

您要等待他们完成吗?如果是这样,请使用 Join 方法。

如果您只想检查它,还有 isAlive 属性。

【讨论】:

请注意,如果线程尚未开始执行,isAlive 返回 false(即使您自己的线程已经对其调用了 start)。 @TomHawtin-tackline 你确定吗?它会与 Java 文档相矛盾(“如果线程已启动且尚未死亡,则它是活动的”-docs.oracle.com/javase/6/docs/api/java/lang/…)。它也会与这里的答案相矛盾(***.com/questions/17293304/…) @Stephen 很久没写了,但这似乎是真的。我想这会给其他人带来九年前记忆犹新的问题。究竟什么是可观察的将取决于实施。您将Thread 告诉start,线程会这样做,但调用会立即返回。 isAlive 应该是一个简单的标志测试,但是当我用谷歌搜索时,方法是 native

以上是关于如何知道其他线程是不是已经完成?的主要内容,如果未能解决你的问题,请参考以下文章

只有在一个线程完成其他依赖方法后才能执行方法

为啥我需要为某些完成块而不是其他块创建自己的线程安全版本?

C# 线程真的可以缓存一个值并忽略其他线程上对该值的更改吗?

J2ME - 如何使线程返回一个值,并在该线程完成后,在其他操作中使用返回值?

我想知道JAVA多线程中,如何让一个线程去等待N个线程执行完成后,再执行。

bool 作为 CheckBox 的数据源(在其他线程中进行 Bool 更新,而不是它影响的线程)