Java线程池的正确关闭方法,awaitTermination还不够
问题说明
今天发现了一个问题,颠覆了我之前对关闭线程池的认识。
一直以来,我坚信用shutdown
+ awaitTermination
关闭线程池是最标准的方式。
不过,这次遇到的问题是,子线程用到BufferedReader
,而BufferedReader
的readLine
是阻塞的,如果流没有关闭那么他一定会一直读取。
即便是awaitTermination
执行完,超时之后返回到主线程。但是子线程没有像预计的那样中断退出,awaitTermination
是不会中断线程的。
BufferedReader reader = ....
String buf;
while ((buf = reader.readLine()) != null) {
buffer.appendBuffer(buf);
}
public static <T> void executeCommand(Callable<T> callable) {
BasicThreadFactory build = new BasicThreadFactory.Builder()
.daemon(false)
.namingPattern("exec-comA")
.build();
ExecutorService executorService = Executors.newSingleThreadExecutor(build);
Future<T> submit = executorService.submit(callable);
executorService.shutdown();
try {
if(!executorService.awaitTermination(60, TimeUnit.SECONDS)){
// 超时的时候向线程池中所有的线程发出中断(interrupted)。
// executorService.shutdownNow();
}
System.out.println("AwaitTermination Finished");
} catch (InterruptedException ignore) {
// executorService.shutdownNow();
}
}
jstack如下:
"exec-comA" #12 prio=5 os_prio=0 tid=0x0000000020f86800 nid=0x419c in Object.wait() [0x0000000021ece000]
java.lang.Thread.State: TIMED_WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x000000076f272cb0> (a com.jcraft.jsch.Channel$MyPipedInputStream)
at java.io.PipedInputStream.read(PipedInputStream.java:326)
- locked <0x000000076f272cb0> (a com.jcraft.jsch.Channel$MyPipedInputStream)
at java.io.PipedInputStream.read(PipedInputStream.java:377)
- locked <0x000000076f272cb0> (a com.jcraft.jsch.Channel$MyPipedInputStream)
at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284)
at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:326)
at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178)
- locked <0x000000076f2837d0> (a java.io.InputStreamReader)
at java.io.InputStreamReader.read(InputStreamReader.java:184)
at java.io.BufferedReader.fill(BufferedReader.java:161)
at java.io.BufferedReader.readLine(BufferedReader.java:324)
这里可以跟进代码,查看PipedInputStream
的读方法,一定是一直在循环中等待数据的 while(in < 0)
。
分析
网友 misakaaaa 评论补充了这个现象的原因,这里对 misakaaaa 表示感谢:
这是因为线程池的 shutdown 方法是通过调用工作线程的 interrupt 方法,来实现终止任务的。
并且刚好你的耗时操作是 BufferedReader 的 readLine 方法,同步 IO 中的阻塞方法没有对中断响应做任何处理。
因此,调用 shutdown 之后,只会把工作线程的中断标志置为 true ,并不会产生实际的效果。
同样的还有内置锁获取时的阻塞,也会产生这样的问题。
结论
用shutdown
+ awaitTermination
关闭线程池是最标准的方式。这话不错,但是这样不能确保子线程按照预想的那样退出。
因此还需要 executorService.shutdownNow();
来主动中断所有子线程。
方法二
import org.apache.commons.exec.Watchdog;
import org.apache.commons.lang3.concurrent.BasicThreadFactory;
import java.io.*;
//....................................................................
Watchdog watchdog = new Watchdog(30000);
Thread thread = Thread.currentThread();
watchdog.addTimeoutObserver(w -> thread.interrupt());
watchdog.start();
try{
//耗时操作
watchdog.stop();
} catch (Exception e) {
e.printStackTrace();
} finally{
//clean some resources
watchdog.stop();
}
这种方式可以使得开发者更加明确的知道,这个耗时任务,超时就要退出终止的。
这样这个世界就会少很多转圈圈。
最后这里是2019年国庆节前最后一篇博客,
恭祝2019年祖国成立70周年。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)