1. 介绍几个API
如果你想探究这些API的原理,可以查看 线程池源码解析+设计思想+线程池监控框架设计 - 掘金 (juejin.cn)
0. 准备
public class Task implements Runnable {
private static final Logger log = LoggerFactory.getLogger(Task.class);
@Getter
private final int id;
public Task(int id) {
this.id = id;
}
@Override
public void run() {
try {
TimeUnit.SECONDS.sleep(1);
System.out.println("第 " + id + " 次开始执行");
} catch (InterruptedException e) {
// 重置打断标记
Thread.currentThread().interrupt();
if (Thread.currentThread().isInterrupted()) {
log.info("第 {} 次任务被打断了", id);
}
}
}
}
1. shutDown()
该API会关闭线程池,拒绝提交新的任务。但是正在执行的任务或者放在任务队列里面的任务不会中断。
public static void shutdown() {
ExecutorService threadPool = Executors.newSingleThreadExecutor();
for (int i = 0; i < 10; i++) {
if (i == 4) {
threadPool.shutdown();
}
try {
threadPool.execute(new Task(i + 1));
} catch (Exception e) {
log.info("第 {} 次任务被拒绝", i + 1);
}
}
//17:40:47.545 [main] INFO com.hdu.Main - 第 5 次任务被拒绝
//17:40:47.547 [main] INFO com.hdu.Main - 第 6 次任务被拒绝
//17:40:47.547 [main] INFO com.hdu.Main - 第 7 次任务被拒绝
//17:40:47.547 [main] INFO com.hdu.Main - 第 8 次任务被拒绝
//17:40:47.547 [main] INFO com.hdu.Main - 第 9 次任务被拒绝
//17:40:47.547 [main] INFO com.hdu.Main - 第 10 次任务被拒绝
//第 1 次开始执行
//第 2 次开始执行
//第 3 次开始执行
//第 4 次开始执行
}
2. shutDownNow()
该API会关闭线程池,拒绝提交新的任务。并且,正在执行的任务会被打断,任务队列里面的任务也会返回。
private static void shutdownNow() {
ExecutorService threadPool = Executors.newSingleThreadExecutor();
for (int i = 0; i < 10; i++) {
try {
threadPool.execute(new Task(i + 1));
} catch (Exception e) {
log.info("第 {} 次任务被拒绝", i + 1);
}
if (i == 4) {
List<Runnable> remainTasks = threadPool.shutdownNow();
for (int j = 0; j < remainTasks.size(); j++) {
Runnable task = remainTasks.get(j);
if (task instanceof Task) {
log.info("第 {} 次任务从任务队列里面移除", ((Task) task).getId());
}
}
}
}
//17:43:00.482 [main] INFO com.hdu.Main - 第 2 次任务从任务队列里面移除
//17:43:00.482 [pool-1-thread-1] INFO com.hdu.Task - 第 1 次任务被打断了
//17:43:00.484 [main] INFO com.hdu.Main - 第 3 次任务从任务队列里面移除
//17:43:00.484 [main] INFO com.hdu.Main - 第 4 次任务从任务队列里面移除
//17:43:00.484 [main] INFO com.hdu.Main - 第 5 次任务从任务队列里面移除
//17:43:00.484 [main] INFO com.hdu.Main - 第 6 次任务被拒绝
//17:43:00.484 [main] INFO com.hdu.Main - 第 7 次任务被拒绝
//17:43:00.484 [main] INFO com.hdu.Main - 第 8 次任务被拒绝
//17:43:00.484 [main] INFO com.hdu.Main - 第 9 次任务被拒绝
//17:43:00.484 [main] INFO com.hdu.Main - 第 10 次任务被拒绝
}
注意,要想被执行的任务被中断,你需要在任务执行逻辑中去相应中断,不然的话线程池永远也关不掉!
@Override
public void run() {
try {
TimeUnit.SECONDS.sleep(1);
System.out.println("第 " + id + " 次开始执行");
} catch (InterruptedException e) {
// 相应中断
// 重置打断标记
Thread.currentThread().interrupt();
if (Thread.currentThread().isInterrupted()) {
log.info("第 {} 次任务被打断了", id);
}
}
}
像上面这样,你必须对线程的中断行为做出响应,不然线程池永远也关不掉。
3. awaitTermination()
该方法用在 线程池关闭后,该方法会等待线程池里面所有的任务执行完成之后才停止阻塞。也就是说你可以等待线程池的所有任务执行完成之后完成一定逻辑。
private static void shutdownAndTermination() {
ExecutorService threadPool = Executors.newSingleThreadExecutor();
for (int i = 0; i < 10; i++) {
try {
threadPool.execute(new Task(i + 1));
} catch (Exception e) {
log.info("第 {} 次任务被拒绝", i + 1);
}
if (i == 4) {
threadPool.shutdown();
}
}
try {
boolean isStop = threadPool.awaitTermination(2, SECONDS);
if (!isStop) {
log.info("线程池超时");
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
//17:46:23.414 [main] INFO com.hdu.Main - 第 6 次任务被拒绝
//17:46:23.416 [main] INFO com.hdu.Main - 第 7 次任务被拒绝
//17:46:23.416 [main] INFO com.hdu.Main - 第 8 次任务被拒绝
//17:46:23.416 [main] INFO com.hdu.Main - 第 9 次任务被拒绝
//17:46:23.416 [main] INFO com.hdu.Main - 第 10 次任务被拒绝
//第 1 次开始执行
//第 2 次开始执行
//17:46:25.429 [main] INFO com.hdu.Main - 线程池超时
//第 3 次开始执行
//第 4 次开始执行
//第 5 次开始执行
}
2. 优雅关闭线程池
优雅关闭线程池的逻辑应该是
- 先调用 shutdown()逻辑
- 调用 awaitTermination(),等待线程池执行处理已经提交的任务。
- 如果超时,调用 shutDownNow()方法,强制中断所有任务
- 调用 awaitTermination()方法,等待那些无法响应中断的任务处理完成。
public static void shutdownGracefully(ExecutorService threadPool) {
if (threadPool != null && !threadPool.isShutdown()) {
log.info("threadPool shutdown");
threadPool.shutdown();
try {
if (!threadPool.awaitTermination(120, SECONDS)) {
List<Runnable> remainTasks = threadPool.shutdownNow();
for (Runnable task : remainTasks) {
if (task instanceof Task) {
log.info("第 {} 次任务从任务队列里面移除", ((Task) task).getId());
// 在这里可以对这些还未执行的任务做一些处理。
}
}
if (!threadPool.awaitTermination(60, SECONDS)) {
log.error("threadPool can not be close");
}
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}