Java并发编程实战-管理Executor生命周期的几个方法

燕雀安知鸿鹄之志?———《史记·陈涉世家》
意思是燕雀怎么能知道鸿鹄的远大志向,比喻平凡的人哪里知道英雄人物的志向。

前文说了 Exectuor 的具体用法,却没有说到如何停止一个任务的执行。所以,本篇文章就来讲解一下 Executor 的生命周期管理。

首先,看下这个框架生命周期相关的主要方法。

1
2
3
4
5
void shutdown();//平缓关闭
List<Runnable> shutdownNow(); //强制关闭
boolean isShutdown(); //是否关闭
boolean isTerminated(); //是否终止
boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException; //timeout时间后是否终止

我们来写例子验证他们作用吧。

创建一个耗时的任务类,模拟下载图片,耗时 5s。后面线程池框架都执行这个任务类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//创建一个耗时任务类
static class ImageDown implements Runnable{
@Override
public void run() {
try {
//正在下载图片
System.out.println("downing image");
Thread.sleep(5000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}

只要这个任务被执行,就会打印 log: downing image。

我们先来看看 shutdown, awaitTermination 的用法。下面这段代码,会创建 5 个线程,执行10个任务。每个任务耗时5s。把所有任务提交给 executorservice 后,调用 shutdown 关闭。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class ExecutorThreadPollTest {
//线程个数
private static final int THREAD_SIZE = 5;

private static ExecutorService executor;

public static void main(String[] args) {
//创建一个线程池
executor = Executors.newFixedThreadPool(THREAD_SIZE);

//提交10个任务
for(int i = 0; i < THREAD_SIZE * 2; i++) {
executor.submit(new ImageDown());
}
//关闭线程池
cancelTask();

try {
//线城池是否关闭,1s判断一次
while(!executor.awaitTermination(1, TimeUnit.SECONDS)) {
System.out.println("Thread Pool is Running");
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

System.out.println("Thread Pool is Terminated");

}
//关闭线程池
static void cancelTask() {
//关闭线程池
executor.shutdown();
System.out.println("shutdown executor");
}
//创建一个耗时任务类
static class ImageDown implements Runnable{
@Override
public void run() {
try {
System.out.println("downing image");
Thread.sleep(5000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}

执行结果如下:

从执行结果看,调用 shutdown 后,任务并没有直接取消,而是等待队列中的所有任务执行完毕才关闭。换句话说, shutdown 是平缓关闭,已经提交的任务会全部执行完再关闭。

另外,awaitTermination 会不断去轮询当前 executorservice 的状态,检查是否已经关闭。这里通过设置它的参数,表示 1s 后会去查一次,看看状态是否变化。若 executorservice 已经关闭,则返回 true, 否则返回 false。

假如上面例子中,在调用 shutdown 后,我们继续提交任务的话,将会被拒绝,抛出异常,整个主线程都会终止。

executor.submit(new ImageDown());

抛出的异常[Shutting down, pool size = 5, active threads = 5, queued tasks = 5, completed tasks = 0]:

1
2
3
4
5
6
Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.FutureTask@75b84c92 rejected from java.util.concurrent.ThreadPoolExecutor@6bc7c054[Shutting down, pool size = 5, active threads = 5, queued tasks = 5, completed tasks = 0]
at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2063)
at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830)
at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1379)
at java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:112)
at ExecutorThreadPollTest.main(ExecutorThreadPollTest.java:23)

那么,要解决这个问题,可以在提交任务的时候,判断 executorservice 是否关闭,如果 isShutDown 返回true, 则不要继续提交任务。相反,则可以继续提交任务。

我们修改一下代码,用shutdownNow来关闭 executorservice.

1
2
3
4
5
6
7
8
9
//关闭线程池
static void cancelTask() {
//关闭线程池
List<Runnable> list = executor.shutdownNow();
System.out.println("is shut down = " + executor.isShutdown());
System.out.println("is Terminated = " + executor.isTerminated());
System.out.println("not running task num = " + list.size());
System.out.println("shutdown executor");
}

执行结果如下:
这里写图片描述
从这个结果看,可以知道当我们调用 shutdownNow 时,任务立马被关闭。所以 awaitTermination 返回的是true, 这个地方没有打印 log。同时,这个 shutdownNow 函数返回工作队列中等待的还未执行的任务。这个还有 5 个任务没有开始执行。

另外, isShutDown 在调用 shutdown 和 shutdownNow 函数后,会立马变为 true。

isTermination 判断当前状态是否是 TERMINATED ,上面 log 有时打印是 true, 有时是 false。说明从 shutdownNow 到 TERMINATED 这个状态,中间还有其他状态的变化跳变,并不是一下子就转到 TERMINATED。

ExecutorService 的状态补充说明如下:

* The runState provides the main lifecycle control, taking on values:
 *
 *   RUNNING:  Accept new tasks and process queued tasks
 *   SHUTDOWN: Don't accept new tasks, but process queued tasks
 *   STOP:     Don't accept new tasks, don't process queued tasks,
 *             and interrupt in-progress tasks
 *   TIDYING:  All tasks have terminated, workerCount is zero,
 *             the thread transitioning to state TIDYING
 *             will run the terminated() hook method
 *   TERMINATED: terminated() has completed
 *   
 * The numerical order among these values matters, to allow
 * ordered comparisons. The runState monotonically increases over
 * time, but need not hit each state. The transitions are:
 *
 * RUNNING -> SHUTDOWN
 *    On invocation of shutdown(), perhaps implicitly in finalize()
 * (RUNNING or SHUTDOWN) -> STOP
 *    On invocation of shutdownNow()
 * SHUTDOWN -> TIDYING
 *    When both queue and pool are empty
 * STOP -> TIDYING
 *    When pool is empty
 * TIDYING -> TERMINATED
 *    When the terminated() hook method has completed
 *
 * Threads waiting in awaitTermination() will return when the
 * state reaches TERMINATED.
 *

本文完结。

坚持原创技术分享,您的支持将鼓励我继续创作!