如何关闭线程池?会创建不会关闭?调用关闭方法时线程池里的线程如何反应? - niceyoo - 博客园
Q1:关闭线程池的方式
关闭线程池有两个方法,分别是:shutdown()、shutdownNow()
- shutdownNow():调用该方法后,首先将线程池的状态设置为 stop,线程池拒绝接受新任务的提交,然后尝试停止所有正在执行或者暂停任务的线程(也就是线程池里现有的任务也不再执行),并返回等待执行任务的列表。
- shutdown():调用该方法后,会将线程池的状态设置为 shutdown,线程池拒绝接受新任务的提交,同时等待线程池内的任务执行完毕之后再关闭线程池。
看完这两个方法的解释,有一个小的结论:调用两个方法后都不会再接收新的任务,调用 shutdownNow() 会 “立刻” 停止线程池里所有的线程(注意,这里的立刻用的双引号,后面会否定这个立刻的),会返回等待执行的任务列表;调用 shutdown() 则会等待线程池里的任务执行完毕之后再关闭线程池,无返回值。
调用两者都会让线程池不再接受新的任务,并且他们的原理都是遍历线程池中的工作线程,然后逐个调用线程的 inputter() 方法来中断线程,而两者的区别是,调用 shutdownNow() 会将线程池设置为 STOP 状态,该方法会返回等待执行的任务列表;而调用 shutdown() 方法会将线程池设置为 SHUTDOWN 状态,无返回值。
总结一下,调用 shutdownNow() 方法时,程池里的线程会有什么反应?
会有两种情况退出线程。(一是正在getTask,二是执行完毕当前任务)
1、当我们调用 shutdownNow() 方法时,如果线程池正在 getTask() 方法中执行,就会通过 for 循环进入 if 语句,判断条件是 标志位 >= SHUTDOWN,或者 标志位 >= STOP,因为符合条件所以会返回 null,然后线程退出。
2、再就是线程执行提交任务到线程池时而处于阻塞状态,就会导致报错抛出 InterruptedException 异常;处于正常运行状态下则会执行完当前任务,然后通过 getTask() 方法返回 null 来退出。
Q2:如何判断线程池里的任务执行完毕了
-
isTerminated()
判断方式,在执行shutdown()
,关闭线程池后,判断是否所有任务已经完成。 -
ThreadPoolExecutor
的getCompletedTaskCount()
方法,判断完成任务数和全部任务数是否相等。 -
CountDownLatch
计数器,使用闭锁计数来判断是否全部完成。 -
手动维护一个公共计数 ,原理和闭锁类似,就是更加灵活。
-
使用
submit
向线程池提交任务,Future
判断任务执行状态。
方式一、isTerminated 方式
private static void shutdownTest() throws Exception {
for (int i = 0; i < 30; i++) {
int index = i;
pool.execute(() -> sleepMtehod(index));
}
pool.shutdown();
while (!pool.isTerminated()){
Thread.sleep(1000);
System.out.println("还没停止。。。");
}
System.out.println("全部执行完毕");
}
在主线程中进行循环判断,全部任务是否已经完成。在执行全部任务后,对线程池进行 shutdown()
有序关闭,然后循环判断 isTerminated()
,线程池是否全部完成。
方式二:getCompletedTaskCount
private static void taskCountTest() throws Exception {
for (int i = 0; i < 30; i++) {
int index = i;
pool.execute(() -> sleepMtehod(index));
}
//当线程池完成的线程数等于线程池中的总线程数
while (!(pool.getTaskCount() == pool.getCompletedTaskCount())) {
System.out.println("任务总数:" + pool.getTaskCount() + "; 已经完成任务数:" + pool.getCompletedTaskCount());
Thread.sleep(1000);
System.out.println("还没停止。。。");
}
System.out.println("全部执行完毕");
}
还是一样在主线程循环判断,主要就两个方法:
-
getTaskCount()
:返回计划执行的任务总数。由于任务和线程的状态可能在计算过程中动态变化,因此返回的值只是一个近似值。 -
getCompletedTaskCount()
:返回完成执行的任务的大致总数。因为任务和线程的状态可能在计算过程中动态地改变,所以返回的值只是一个近似值,但是在连续的调用中并不会减少。
方式三:CountDownLatch 计数器
private static void countDownLatchTest() throws Exception {
//计数器,判断线程是否执行结束
CountDownLatch taskLatch = new CountDownLatch(30);
for (int i = 0; i < 30; i++) {
int index = i;
pool.execute(() -> {
sleepMtehod(index);
taskLatch.countDown();
System.out.println("当前计数器数量:" + taskLatch.getCount());
});
}
//当前线程阻塞,等待计数器置为0
taskLatch.await();
System.out.println("全部执行完毕");
}
这种方法,呃,应该是看起来比较高级的,我也不知道别的大佬怎么写的,反正我就用这个。
缺点 :需要提前知道线程数量
方式四:Future 判断任务执行状态
Future
是用来装载线程结果的,不过,用这个来进行判断写代码总感觉怪怪的。
因为 Future
只能装载一条线程的返回结果,多条线程总不能用 List 在接收 Future 。
这里就开一个线程做个演示:
private static void futureTest() throws Exception {
Future<?> future = pool.submit(() -> sleepMtehod(1));
while (!future.isDone()){
Thread.sleep(500);
System.out.println("还没停止。。。");
}
System.out.println("全部执行完毕");
}
这种方式就不写优缺点了,因为 Future
的主要使用场景并不是用于判断任务执行状态。
Q3:Android中的四种线程池
1. newCachedThreadPool() 缓存线程池
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
核心线程数为0, 线程池的最大线程数Integer.MAX_VALUE。而Integer.MAX_VALUE是一个很大的数,也差不多可以说 这个线程池中的最大线程数可以任意大。
对于newCachedThreadPool他的任务队列采用的是SynchronousQueue,上面说到在SynchronousQueue内部没有任何容量的阻塞队列。SynchronousQueue内部相当于一个空集合,我们无法将一个任务插入到SynchronousQueue中。所以说在线程池中如果现有线程无法接收任务,将会创建新的线程来执行任务。
2. newFixedThreadPool 定长线程池
ExecutorService service = Executors.newFixedThreadPool(4);
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
在这个线程池中 所容纳最大的线程数就是我们设置的核心线程数。 如果线程池的线程处于空闲状态的话,它们并不会被回收,除非是这个线程池被关闭。如果所有的线程都处于活动状态的话,新任务就会处于等待状态,直到有线程空闲出来。
由于newFixedThreadPool只有核心线程,并且这些线程都不会被回收,也就是 它能够更快速的响应外界请求 。从下面的newFixedThreadPool方法的实现可以看出,newFixedThreadPool只有核心线程,并且不存在超时机制,采用LinkedBlockingQueue,所以对于任务队列的大小也是没有限制的。
3. newSingleThreadExecutor() 单一线程的线程池
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
通过Executors中的newSingleThreadExecutor方法来创建,在这个线程池中只有一个核心线程,对于任务队列没有大小限制,也就意味着这一个任务处于活动状态时,其他任务都会在任务队列中排队等候依次执行。
newSingleThreadExecutor将所有的外界任务统一到一个线程中支持,所以在这个任务执行之间我们不需要处理线程同步的问题。
4. newScheduledThreadPool 定时,定周期线程池 类似于Timer。
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
它的核心线程数是固定的,对于非核心线程几乎可以说是没有限制的,并且当非核心线程处于限制状态的时候就会立即被回收。