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

时间:2009-03-31 18:25:10

标签: java multithreading

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

如何在每个线程执行完毕后收到通知?

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

12 个答案:

答案 0 :(得分:220)

有很多方法可以做到这一点:

  1. 在主线程中使用Thread.join()以阻塞方式等待每个线程完成,或者
  2. 以轮询方式检查Thread.isAlive() - 通常不鼓励 - 等到每个线程完成,或
  3. 非正统,对于每个有问题的线程,调用setUncaughtExceptionHandler来调用对象中的方法,并编写每个线程以在完成时抛出未被捕获的异常,或者
  4. 使用java.util.concurrent
  5. 中的锁定或同步器或机制
  6. 更正统,在主线程中创建一个监听器,然后对每个线程进行编程,告诉听众他们已经完成了。
  7. 如何实现Idea#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并在创建每个Thread后立即将其自身添加到列表中听众:

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

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

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

答案 1 :(得分:13)

使用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个进入它,包括执行的主线程,主线程将仅在所有其他线程完成下载后继续执行。

答案 2 :(得分: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

答案 3 :(得分:4)

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

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

答案 4 :(得分:4)

您可以使用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个孩子完成时,主人将继续执行。

答案 5 :(得分:2)

我建议查看Thread类的javadoc。

您有多种线程操作机制。

  • 您的主线程可以join()连续三个线程,然后在完成所有三个线程之后才会继续。

  • 定期轮询生成的线程的线程状态。

  • 将所有衍生线程放入单独的ThreadGroup并轮询activeCount()上的ThreadGroup并等待它到达0。

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

我确信还有很多其他方法我仍然缺席。

答案 6 :(得分:1)

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

答案 7 :(得分:1)

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

您可以使用

,而不是使用join()和锁定API

1。ExecutorService invokeAll() API

  

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

2。CountDownLatch

  

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

     

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

3. ForkJoinPool中的ExecutorsnewWorkStealingPool()是另一种方式

4.在Future上对提交的所有ExecutorService个任务进行迭代,并在get()对象上使用屏蔽Future检查状态

查看相关的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?

答案 8 :(得分:1)

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

(1)我创建了全局变量:boolean end1 = false;线程在结束时将其设置为true。这是通过&#34; postDelayed&#34;在主线程中获得的。循环,它被响应。

(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)幸运的是,&#34; postDelayed&#34;在主线程中运行,以便每秒检查另一个线程一次。当另一个线程结束时,这可以从我们想要做的任何事情开始。

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

答案 9 :(得分:1)

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

  1. 它有一个队列,您可以设置要并行处理的线程数。
  2. 它具有不错的回调方法:
  

挂钩方法

     

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

这正是我们所需要的。每个线程完成后,我们将覆盖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方法,因为您是并行进行工作的,如果决定从另一个{{ 1}}方法!这通常会导致死锁

希望这会有所帮助!

答案 10 :(得分:0)

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

但是,你必须要小心,因为你可以在线程之间引起竞争条件。试着避免基于其他线程状态的复杂逻辑。绝对避免多个线程写入相同的变量。

答案 11 :(得分:0)

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