注册钩子函数
private static void shutDownThreadPool(){
Runtime.getRuntime().addShutdownHook( new ShutdownHookThread(log,() -> {
poolExecuteConcurrentHashMap.forEach( (k,v) -> {
if(Optional.ofNullable( v ).isPresent())
v.shutdown();
} );
return null;
}) );
}
public class ShutdownHookThread extends Thread {
private volatile boolean hasShutdown = false;
private AtomicInteger shutdownTimes = new AtomicInteger(0);
private final Logger log;
private final Callable callback;
public ShutdownHookThread(Logger log, Callable callback) {
super("ShutdownHook");
this.log = log;
this.callback = callback;
}
@Override
public void run() {
synchronized (this) {
log.info("shutdown hook was invoked, " + this.shutdownTimes.incrementAndGet() + " times.");
if (!this.hasShutdown) {
this.hasShutdown = true;
long beginTime = System.currentTimeMillis();
try {
this.callback.call();
} catch (Exception e) {
log.error("shutdown hook callback invoked failure.", e);
}
long consumingTimeTotal = System.currentTimeMillis() - beginTime;
log.info("shutdown hook done, consuming time total(ms): " + consumingTimeTotal);
}
}
}
}
对于通过注册ShutdownHook实现的优雅退出,需要注意如下几点,防止踩坑
(1)ShutdownHook在某些情况下并不会被执行,例如JVM崩溃、无法接收信号量和kill-9 pid等。
(2)当存在多个ShutdownHook时,JVM无法保证它们的执行先后顺序。
(3)在JVM关闭期间不能动态添加或者去除ShutdownHook。
(4)不能在ShutdownHook中调用System.exit(),它会卡住JVM,导致进程无法退出。
信号量回调函数
Signal signal = new Signal( System.getProperty( "os.name" ).toLowerCase().startsWith( "win" ) ? "INT" : "TERM" );
Signal.handle( signal,signal1 -> {
System.out.println("释放资源");
} );
其中Signal构造函数的参数为String字符串,它代表了操作系统支持的信号量列表(此处注意:不同操作系统支持的信号量不同),如表1-1所示为Linux支持的一些常用信号量。
判断是否是Windows操作系统,如果是则选择SIGINT,接收Ctrl+C中断的指令,否则选择TERM信号,接收SIGTERM(等价于kill pid)指令(备注:这里仅是支持Windows和Linux操作系统的代码示例)。
将实例化之后的SignalHandler注册到JDK的Signal,一旦Java进程接收到killpid或Ctrl+C,则回调handle接口:
对于采用注册 SignalHandler 实现优雅退出的程序,在 handle 接口中一定要避免阻塞操作,否则它会导致已经注册的 ShutdownHook无法执行,系统也无法退出