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

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

当每个线程完成执行时,如何获得通知?

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

231530 次浏览

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

如果您只想检查 isAlive 属性,也可以使用它。

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

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

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

*  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个已经完成。

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

  1. 在主线程中使用 Join ()以阻塞方式等待每个线程完成,或者
  2. 以轮询的方式检查 Thread.isAlive ()(通常不鼓励这样做) ,以等待每个线程完成,或者
  3. 对于每个有问题的线程,调用 异常处理程序来调用对象中的方法,并编写程序使每个线程在完成时抛出一个未捕获的异常,或者
  4. 使用来自 并发的锁或同步器或机制,或
  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()。因此,当它们完成时,它们将自动通知任何等待通知的人。

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

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

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

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

我建议查看 线头类的 javadoc。

您有多种线程操作机制。

  • 您的主线程可以 join()这三个线程串行,然后将不会继续,直到所有三个完成。

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

  • 将所有衍生的线程放入一个单独的 ThreadGroup中,并在 ThreadGroup上轮询 activeCount(),然后等待它变为0。

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

我肯定还有很多其他的方法没找到。

使用 循环屏障的解决方案

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-th DownloadingThread 进入等候室

标签3 -NUMBER _ OF _ DOWNLOADING _ THREADS 已进入候车室。执行的主线程会释放它们,让它们在大致相同的时间内开始下载作业

Label 4 -执行的主线程进入等候室。这是代码中最难理解的部分。不管哪个线程会第二次进入候诊室。最后进入房间的线程必须确保所有其他下载线程都完成了它们的下载任务,这一点很重要。

Label 2 -n-th DownloadingThread 已经完成了它的下载工作并进入了等待室。如果是最后一个,即 NUMBER _ OF _ DOWNLOADING _ THREADS 已经输入了它,包括执行的主线程,那么只有当所有其他线程都完成下载后,主线程才会继续执行。

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

还可以使用 Executors对象创建 执行服务线程池。然后使用 invokeAll方法运行每个线程并检索 Futures。这将一直阻塞到所有人都完成执行为止。您的另一种选择是使用池执行每一个,然后调用 awaitTermination来阻塞,直到池完成执行。只要确保在添加任务完成后调用 shutdown()即可。

你应该 真的更喜欢使用 java.util.concurrent的解决方案。找到并阅读 Josh Bloch 和/或 Brian Goetz 关于这个主题的文章。

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

public interface CallbackRunnable extends Runnable {
public void callback();
}

然后创建一个 Execator,它将执行您的 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

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

不使用 join()和锁 API,您可以使用

1. 执行服务

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

2. CountDownLatch

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

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

3. 执行者中的 ForkJoinPoolnewWorkStealingPool()是另一种方式

4. 在 ExecutorService上迭代所有 Future任务的提交,并在 Future对象上用阻塞呼叫 get()检查状态

看看相关的社会工作问题:

如何等待一个线程,产生它自己的线程?

执行者: 如果任务是递归创建的,那么如何同步等待,直到所有任务都完成?

这里有一个简单,简短,易于理解的解决方案,对我来说非常有效。当另一个线程结束时,我需要画到屏幕上; 但是不能,因为主线程控制了屏幕。所以:

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

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

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

钩法

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

这正是我们需要的。我们将覆盖 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. And start your threads:

    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
    }
    

Inside method informUiThatWeAreDone(); do whatever you need to do when all threads are done, for example, update UI.

NOTE: Don't forget about using synchronized methods since you do your work in parallel and BE VERY CAUTIOUS if you decide to call synchronized method from another synchronized method! This often leads to deadlocks

Hope this helps!