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

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

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

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

【问题讨论】:

标签: java multithreading


【解决方案1】:

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

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

挂钩方法

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

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

这就是你应该做的事情

  1. 创建一个执行器:

    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();
        }
    
    }
    
  2. 然后开始你的线程:

    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.ForkJoinPoolnewWorkStealingPool() 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】:

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

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

                  如何实现想法#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/…)。它也会与这里的答案相矛盾(stackoverflow.com/questions/17293304/…
                  • @Stephen 很久没写了,但这似乎是真的。我想这会给其他人带来九年前记忆犹新的问题。究竟什么是可观察的将取决于实施。您将Thread 告诉start,线程会这样做,但调用会立即返回。 isAlive 应该是一个简单的标志测试,但是当我用谷歌搜索时,方法是 native
                  猜你喜欢
                  • 2020-02-06
                  • 1970-01-01
                  • 2011-01-30
                  • 1970-01-01
                  • 2012-03-06
                  • 1970-01-01
                  • 1970-01-01
                  • 2013-11-08
                  • 1970-01-01
                  相关资源
                  最近更新 更多