1, ShutdownHook初识
在Java程序中可以通过添加关闭钩子,实现在程序退出时关闭资源、平滑退出的功能。 并且在以下几种场景将调用该钩子
- 程序正常退出
- 使用System.exit()
- 终端使用Ctrl+C触发的中断
- 系统关闭
- 使用Kill pid命令干掉进程
具体来讲Runtime.addShutdownHook 添加钩子到 ApplicationShutdownHooks中。
// Runtime添加钩子(钩子具体来讲就是一个要执行的线程任务)
public void addShutdownHook(Thread hook) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(new RuntimePermission("shutdownHooks"));
}
ApplicationShutdownHooks.add(hook);
}
// Runtime去除钩子
public boolean removeShutdownHook(Thread hook) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(new RuntimePermission("shutdownHooks"));
}
return ApplicationShutdownHooks.remove(hook);
}
再看ApplicationShutdownHooks
class ApplicationShutdownHooks {
/* The set of registered hooks */
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;
}
}
private ApplicationShutdownHooks() {}
/* Add a new shutdown hook. Checks the shutdown state and the hook itself,
* but does not do any security checks.
*/
static synchronized void add(Thread hook) {
if(hooks == null)
throw new IllegalStateException("Shutdown in progress");
if (hook.isAlive())
throw new IllegalArgumentException("Hook already running");
if (hooks.containsKey(hook))
throw new IllegalArgumentException("Hook previously registered");
hooks.put(hook, hook);
}
/* Remove a previously-registered hook. Like the add method, this method
* does not do any security checks.
*/
static synchronized boolean remove(Thread hook) {
if(hooks == null)
throw new IllegalStateException("Shutdown in progress");
if (hook == null)
throw new NullPointerException();
return hooks.remove(hook) != null;
}
/* Iterates over all application hooks creating a new thread for each
* to run in. Hooks are run concurrently and this method waits for
* them to finish.
*/
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) {
}
}
}
}
}
2, 自定义hooks
往往在应用程序里已经有了一些第三方注册好的hook, 当我们要对将自己自定义的hook放到其他hook之前执行,需要强制干预ApplicationShutdownHooks的hooks,可以通过反射来做
String className = "java.lang.ApplicationShutdownHooks";
Class<?> clazz = Class.forName(className);
Field field = clazz.getDeclaredField("hooks");
field.setAccessible(true);
// 先反射拿到其他的hook,
IdentityHashMap<Thread, Thread> otherHookMap = (IdentityHashMap<Thread, Thread>) field.get(clazz);
// 将otherHookMap 中 各个hook封装一个延时(比如3s)
// 再将自定义hook 加入
Runtime.getRuntime().addShutdownHook(new Thread(()->{ System.out.println("exec my hook"); }));
3, ShenYu中应用
ShenYu项目下线时,会将一些client服务,比如注册中心客户端,优雅停掉。
public class ShenyuClientShutdownHook {
// 钩子名称
private static String hookNamePrefix = "ShenyuClientShutdownHook";
private static AtomicInteger hookId = new AtomicInteger(0);
private static Properties props;
private static AtomicBoolean delay = new AtomicBoolean(false);
//等待加入延时,但是还没有加入的hooks
private static IdentityHashMap<Thread, Thread> delayHooks = new IdentityHashMap<>();
//已经做了延时的hook
private static IdentityHashMap<Thread, Thread> delayedHooks = new IdentityHashMap<>();
/**
* Add shenyu client shutdown hook.
*
* @param result ShenyuClientRegisterRepository
* @param props Properties
*/
public static void set(final ShenyuClientRegisterRepository result, final Properties props) {
String name = hookNamePrefix + "-" + hookId.incrementAndGet();
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
result.close();
}, name));
log.info("Add hook {}", name);
ShenyuClientShutdownHook.props = props;
}
/**
* Delay other shutdown hooks.// 将其他的hooks做延时处理
*/
public static void delayOtherHooks() {
if (!delay.compareAndSet(false, true)) {
return;
}
TakeoverOtherHooksThread thread = new TakeoverOtherHooksThread();
thread.start();
}
/**
* Delay other shutdown hooks thread.
* 延时处理的线程, 1,反射拿到其他hooks
* 2,遍历hooks,通过线程包装原先的hook线程任务,并在其中加3s的延时
* 3,将原先的hook线程任务重新加入到ApplicationShutdownHooks中
*/
private static class TakeoverOtherHooksThread extends Thread {
@SneakyThrows
@Override
public void run() {
int shutdownWaitTime = Integer.parseInt(props.getProperty("shutdownWaitTime", "3000"));
int delayOtherHooksExecTime = Integer.parseInt(props.getProperty("delayOtherHooksExecTime", "2000"));
Class<?> clazz = Class.forName(props.getProperty("applicationShutdownHooksClassName", "java.lang.ApplicationShutdownHooks"));
Field field = clazz.getDeclaredField(props.getProperty("applicationShutdownHooksFieldName", "hooks"));
field.setAccessible(true);
IdentityHashMap<Thread, Thread> hooks = (IdentityHashMap<Thread, Thread>) field.get(clazz);
long s = System.currentTimeMillis();
while (System.currentTimeMillis() - s < delayOtherHooksExecTime) {
for (Iterator<Thread> iterator = hooks.keySet().iterator(); iterator.hasNext();) {
Thread hook = iterator.next();
if (hook.getName().startsWith(hookNamePrefix)) {// 为当前自己自定义的hook 就不做延时
continue;
}
if (delayHooks.containsKey(hook) || delayedHooks.containsKey(hook)) {
continue;
}
Thread delayHook = new Thread(() -> {// 通过线程包装原先的hook线程任务,并在其中加3s的延时
log.info("sleep {}ms", shutdownWaitTime);
try {
TimeUnit.MILLISECONDS.sleep(shutdownWaitTime);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
hook.run();
}, hook.getName());
delayHooks.put(delayHook, delayHook);// 加入的待加入延时集合
iterator.remove();// 从原来的ApplicationShutdownHooks的hooks集合中去掉
}
for (Iterator<Thread> iterator = delayHooks.keySet().iterator(); iterator.hasNext();) {
Thread delayHook = iterator.next();
Runtime.getRuntime().addShutdownHook(delayHook);// 加入到ApplicationShutdownHooks的hooks集合中去掉
delayedHooks.put(delayHook, delayHook);
iterator.remove();
log.info("hook {} will sleep {}ms when it start", delayHook.getName(), shutdownWaitTime);
}
TimeUnit.MILLISECONDS.sleep(100);
}
hookNamePrefix = null;
hookId = null;
props = null;
delayHooks = null;
delayedHooks = null;
}
}
}