优雅的关闭Java线程池,这样做才是yyds

1 背景某年某月某日,和我的卧龙同事聊一个需求,说是有个数据查询的功能 , 因为涉及到多个第三方接口调用,想用线程池并行来做 。
很正常的一个方案,但是上线后发现,每次服务发布的时候,这个数据查询的功能就会挂掉,后来发现是线程池没有做好关闭,这里总结一下 。
关键字:线程池;shutdown;shutdownNow;interrupt
2 线程中断 interrupt先补一补基础的知识:线程中断 。
线程中断的含义 , 并不是强制把运行中的线程给“咔嚓”中断,而是把线程的中断标志位置为true , 这样等线程之后阻塞(wAIt、join、sleep)的时候 , 就会抛出 InterruptedException,程序通过捕获 InterruptedException 来做一定的善后处理,然后让线程退出 。
来看个例子,下面这段代码是起一个线程,打印一百行文本,打印过程中,会把线程的中断标志位置为true
public static void test02() throws InterruptedException {Thread t = new Thread(() -> {for (int i = 0; i < 100; i++) {System.out.println("process i=" + i + ",interrupted:" + Thread.currentThread().isInterrupted());}});t.start();Thread.sleep(1);t.interrupt();}看看控制台的输出,发现在打印到 57 的时候,中断标志位已经成功置为true了,但是线程任然在打印 , 说明只是设置了中断标志位 , 而不是直接粗暴的把线程中断 。
...
process i=55,interrupted:false
process i=56,interrupted:false
process i=57,interrupted:true
process i=58 , interrupted:true
process i=59,interrupted:true
...
【优雅的关闭Java线程池,这样做才是yyds】再看看这个示例,同样是打印一百行文本,打印过程中会判断中断标志位,如果中断就自行退出 。
public static void test02() throws InterruptedException {Thread t = new Thread(() -> {for (int i = 0; i < 100; i++) {if (Thread.interrupted()) {System.out.println("线程已中断,退出执行");break;}System.out.println("process i=" + i + ",interrupted:" + Thread.currentThread().isInterrupted());}});t.start();Thread.sleep(1);t.interrupt();}控制台输出如下 , :
process i=49 , interrupted:false
process i=50,interrupted:false
process i=51,interrupted:false
线程已中断,退出执行
3 线程池的关闭 shutdown 方法了解完线程中断 , 再来看看线程池的关闭方法 。
关闭线程池有两个方法 shutdown() 和 shutdownNow(),具体有什么区别?我们先来看看 shutdown() 方法
/** * Initiates an orderly shutdown in which previously submitted * tasks are executed, but no new tasks will be accepted. * Invocation has no additional effect if already shut down. * * <p>This method does not wait for previously submitted tasks to * complete execution.Use {@link #awaitTermination awaitTermination} * to do that. * * @throws SecurityException {@inheritDoc} */public void shutdown() {final ReentrantLock mainLock = this.mainLock;mainLock.lock();try {checkShutdownAccess();advanceRunState(SHUTDOWN); // 1. 把线程池的状态设置为 SHUTDOWNinterruptIdleWorkers(); // 2. 把空闲的工作线程置为中断onShutdown(); // 3. 一个空实现,暂不用关注} finally {mainLock.unlock();}tryTerminate();}看源码先看注释,我用我英语四级的超高水准水平翻译下:
启动有序关闭会执行以前提交的任务,但不接受任何新任务 。
如果已经关闭,则调用不会产生额外的影响 。
此方法不等待活动执行的任务终止 。如果需要,可使用 awaitTermination() 做到这一点 。
3.1 第一步:advanceRunState(SHUTDOWN) 把线程池置为 SHUTDOWN线程池状态流转如下 。调用 shutdown() 方法会把线程池的状态置为 SHUTDOWN,后续再往线程池提交任务就会被拒绝(execute() 方法中做了判断) 。

优雅的关闭Java线程池,这样做才是yyds

文章插图
3.2 第二步:interruptIdleWorkers() 把空闲的工作线程置为中断interruptIdleWorkers() 方法遍历所有的工作线程 , 如果 tryLock() 成功,就把线程置为中断 。
这里,如果 tryLock() 成功,说明对应的 woker 是一个空闲的,没有在执行任务的线程,如果没成功,说明对应的 worker 正在执行任务 。也就是说,这里的中断,对正在执行中的任务并没有影响 。
private void interruptIdleWorkers(boolean onlyOne) {final ReentrantLock mainLock = this.mainLock;mainLock.lock();try {for (Worker w : workers) {Thread t = w.thread;if (!t.isInterrupted() && w.tryLock()) {try {t.interrupt();} catch (SecurityException ignore) {} finally {w.unlock();}}if (onlyOne)break;}} finally {mainLock.unlock();}}


推荐阅读