使用场景
- 关闭资源
- 保存程序状态
- MQ消费安全
如何使用
Runtime.getRuntime().addShutdownHook(new Thread(){
@Override
public void run() {
System.out.println("I'm shutdown hook...");
}
});
在中间件和框架中的使用
RocketMq release-4.3.0
// org.apache.rocketmq.broker.BrokerStartup#createBrokerController
public static BrokerController createBrokerController(String[] args) {
...
Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
private volatile boolean hasShutdown = false;
private AtomicInteger shutdownTimes = new AtomicInteger(0);
@Override
public void run() {
synchronized (this) {
log.info("Shutdown hook was invoked, {}", this.shutdownTimes.incrementAndGet());
if (!this.hasShutdown) {
this.hasShutdown = true;
long beginTime = System.currentTimeMillis();
controller.shutdown();
long consumingTimeTotal = System.currentTimeMillis() - beginTime;
log.info("Shutdown hook over, consuming total time(ms): {}", consumingTimeTotal);
}
}
}
}, "ShutdownHook"));
}
实现原理
1、当我们添加一个ShutdownHook时,会调用ApplicationShutdownHooks.add(hook),往ApplicationShutdownHooks类下的静态变量private static IdentityHashMap<Thread, Thread> hooks添加一个hook,hook本身是一个thread对象
2、ApplicationShutdownHooks类初始化时会把hooks添加到Shutdown的hooks中去
// java.lang.ApplicationShutdownHooks
private static IdentityHashMap<Thread, Thread> hooks;
static {
try {
Shutdown.add(1 /* shutdown hook invocation order */,
false /* not registered if shutdown in progress */,
new Runnable() {
public void run() {
runHooks();
}
}
);
hooks = new IdentityHashMap<>();
} catch (IllegalStateException e) {
// application shutdown hooks cannot be added if
// shutdown is in progress.
hooks = null;
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GuVH9T7Y-1675508025694)(evernotecid://4143277E-C9B7-4F5C-948C-63A95AD76A67/appyinxiangcom/20976546/ENNote/p880?hash=b01f3a2b036137a6231593be6ad05a5a)]
系统Shutdown仅仅支持10个slot
系统设定 slot=1为应用注册的hook
// java.lang.Shutdown
// The system shutdown hooks are registered with a predefined slot.
// The list of shutdown hooks is as follows:
// (0) Console restore hook
// (1) Application hooks
// (2) DeleteOnExit hook
private static final int MAX_SYSTEM_HOOKS = 10;
顺序遍历每个slot,并发执行每个slot里面注册的hooks
private static IdentityHashMap<Thread, Thread> hooks;
// java.lang.Shutdown#runHooks
private static void runHooks() {
for (int i=0; i < MAX_SYSTEM_HOOKS; i++) {
try {
Runnable hook;
synchronized (lock) {
// acquire the lock to make sure the hook registered during
// shutdown is visible here.
currentRunningHook = i;
hook = hooks[i];
}
if (hook != null) hook.run();
} catch(Throwable t) {
if (t instanceof ThreadDeath) {
ThreadDeath td = (ThreadDeath)t;
throw td;
}
}
}
}
并发执行每个slot里面注册的hooks
// java.lang.ApplicationShutdownHooks#runHooks
static void runHooks() {
Collection<Thread> threads;
synchronized(ApplicationShutdownHooks.class) {
threads = hooks.keySet();
hooks = null;
}
for (Thread hook : threads) {
hook.start();
}
for (Thread hook : threads) {
while (true) {
try {
hook.join();
break;
} catch (InterruptedException ignored) {
}
}
}
}
ShutdownHook触发点
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KNbaoL51-1675508025695)(evernotecid://4143277E-C9B7-4F5C-948C-63A95AD76A67/appyinxiangcom/20976546/ENNote/p880?hash=bbef4e2bbd683b4ecd011a9c04e4ca08)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4CWxD8IE-1675508025695)(evernotecid://4143277E-C9B7-4F5C-948C-63A95AD76A67/appyinxiangcom/20976546/ENNote/p880?hash=c960431444d0027391dd6a911d14b6f3)]
Shutdown.exit
Shutdown.exit的调用方,发现有 Runtime.exit 和 Terminator.setup
- Runtime.exit
代码中主动结束进程的接口 - Terminator.setup
Terminator.setup 被 initializeSystemClass 调用,当第一个线程被初始化的时候被触发,触发后注册了一个信号监控函数,捕获kill发出的信号,调用Shutdown.exit结束进程
实现SignalHandler接口以及handle方法,程序入口处注册要监听的相应信号
// java.lang.Terminator
static void setup() {
if (handler == null) {
SignalHandler var0 = new SignalHandler() {
public void handle(Signal var1) {
Shutdown.exit(var1.getNumber() + 128);
}
};
handler = var0;
try {
Signal.handle(new Signal("HUP"), var0);
} catch (IllegalArgumentException var4) {
}
try {
Signal.handle(new Signal("INT"), var0);
} catch (IllegalArgumentException var3) {
}
try {
Signal.handle(new Signal("TERM"), var0);
} catch (IllegalArgumentException var2) {
}
}
}
Shurdown.shutdown()
该方法会在最后一个非daemon线程(非守护线程)结束时被JNI的DestroyJavaVM方法调用。
注意:当我们杀死进程时使用了kill -9时,由于程序无法捕获处理,进程被直接杀死,所以无法执行ShutdownHook。
参考:
1、ShutdownHook原理