线程池如何知道一个线程已经执行完毕【超详细版】
前言:线程池的使用可以提高我们并发程序线程复用,以及提供对线程的管理能力。提高线程的利用率,提升性能。但是对于一些场景我们需要知道当前线程池中提交的任务是否执行完毕,我们可以通过以下几种方式判断。
问题分析:首先,从线程池内部来看,我们知道,1. 在线程池内部,当我们把一个任务丢给线程池去执行,线程池会调度工作线程来执行这个任务的 run 方法,run 方法正常结束,也就意味着任务完成了。
而从线程池外部来看:
1.使用线程池的isTerminated ()方法
方法说明:threadPool.isTerminated() 常用来判断线程池是否结束,结束了为TRUE. 使用threadPool.isTerminated() 方法,必须在shutdown()方法关闭线程池之后才能使用,否则isTerminated()永不为TRUE,线程将一直阻塞在该判断的地方,导致程序最终崩溃。
代码示例:
package test;
import java.util.concurrent.*;
/**
* @author 何勇进
* @version 1.0
*/
public class PoolText {
public static void main(String[] args) {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor
(10, 20, 0, TimeUnit.SECONDS, new LinkedBlockingDeque<>(1024));
//首先,创建一个线程池
addTask(threadPoolExecutor);
//调用addTask方法 启用一个线程,
isCompleted(threadPoolExecutor);//关闭线程池,并在线程结束后输出 isTerminated() 就为true了
System.out.println("线程任务执行完毕");
}
private static void addTask(ThreadPoolExecutor threadPool){
threadPool.execute(new Runnable() {
@Override
public void run() {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("任务执行完毕");
}
});
}
private static void isCompleted(ThreadPoolExecutor threadPool){
threadPool.shutdown();
try {
TimeUnit.SECONDS.sleep(3);//延迟3s 是为了让线程现行执行完毕 ,如果小于2s关闭线程池
//是在线程工作完毕之前,则会返回false.
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(threadPool.isTerminated());
}
}
但是我们知道,在实际业务中,我们并不会去主动的关闭线程池,所以这个方法在灵活性和实用性方面并不是很好
2.使用 getCompletedTaskCount 方法
我们可以通过判断线程池中的计划执行任务数和已完成任务数,来判断线程池是否已经全部执行完,如果计划执行任务数=已完成任务数,那么线程池的任务就全部执行完了,否则就未执行完。
代码示例:
private static void isCompletedByTaskCount(ThreadPoolExecutor threadPool) {
while (threadPool.getTaskCount() != threadPool.getCompletedTaskCount()) {
}
}
说明:
1.getTaskCount():返回计划执行的任务总数。由于任务和线程的状态可能在计算过程中动态变化,因此返回的值只是一个近似值。
2.getCompletedTaskCount():返回完成执行任务的总数。因为任务和线程的状态可能在计算过程中动态地改变,所以返回的值只是一个近似值,但是在连续的调用中并不会减少。
这个方法的好处是,无需关闭线程池便可知道线程是否工作完毕,但是由于任务和线程的状态可能在计算过程中动态的变化,故返回的值只能是一个近似值。
3. 使用 CountDownLatch
CountDownLatch 是一个栅栏类似计数器,我们创建了一个包含 N 个任务的计数器,每个任务执行完计数器 -1,直到计数器减为 0 时,说明所有的任务都执行完了
代码示例:
package test;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* @author 何勇进
* @version 1.0
*/
public class PoolText2 {
public static void main(String[] args) {
int workNum = 10;
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(10, 20,
0, TimeUnit.SECONDS, new LinkedBlockingDeque<>(1024));
CountDownLatch countDownLatch = new CountDownLatch(workNum);
for (int i = 0; i < workNum; i++) {
int finalI = i;
threadPool.submit(() -> {
try {
int sleepTime = new Random().nextInt(5);
TimeUnit.SECONDS.sleep(Math.max(sleepTime, 2));
} catch (InterruptedException e) {
System.err.println("InterruptedException err : " + e.getMessage());
}
System.out.println(" task running : " + finalI);
countDownLatch.countDown();
});
}
try {
countDownLatch.await();
System.out.println("所有线程均执行完毕");
threadPool.shutdown();
} catch (InterruptedException t) {
System.err.println("InterruptedException err : " + t.getMessage());
}
}
}
说明:
这里面用到了两个方法:一个是countDown() ,一个是,await().
1.countDown():进行倒计时,即当一个线程执行完毕后, CountDownLatch计数器-1.
2.await():阻塞主线程,当CountDownLatch计数器减为0时,即传入到线程池中的任务执行完毕时,才会唤醒阻塞在await()方法的线程.
则基于这样的原理:基于这样的原理,我们可以定义一个 CountDownLatch 对象并且计数器为 1,接着在 线程池代码块后面调用 await()方法阻塞主线程,然后,当传入到线程池中的任务执行完成后,调用 countDown()方法表示任务执行结束。 最后,计数器归零 0,唤醒阻塞在 await()方法的线程。
总结:
不管是线程池内部还是外部,要想知道线程是否执行结束,我们必须要获取线程执行结束后的状态,而线程本身没有返回值,所以 只能通过阻塞-唤醒的方式来实现
参考文章:https://www.51cto.com/article/720389.html
因篇幅问题不能全部显示,请点此查看更多更全内容