一个有效的收拾程序运行残局的方法--ShutdownHook

发现addShutdownHook

在阅读QMQ的源码的时候,在Server端启动的时候,注册了一个shutdown的代码,具体的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
 Runtime.getRuntime().addShutdownHook(new Thread(wrapper::destroy));

```

#### addShutdownHook作用
`addShutdownHook`方法可以添加一个指定的线程来在Java程序退出的时候做一些事情,在以下几个场景会被调用:

1. 程序运行完成后退出
2. 使用Ctrl+C时终端退出
3. 调用系统退出的方法, `System.exit(0)`

具体的使用Demo如下:

``` java
public class AppMain {
public static void main(String[] args) {
Runtime.getRuntime().addShutdownHook(new Thread(AppMain::exit01));
Runtime.getRuntime().addShutdownHook(new Thread(AppMain::exit02));
Runtime.getRuntime().addShutdownHook(new Thread(AppMain::exit03));
while (true){
System.out.println(".....");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}

}
public static void exit01(){
System.out.println("exit01");
}
public static void exit02(){
System.out.println("exit02");
}
public static void exit03(){
System.out.println("exit03");
}
}

```

执行当执行退出的时候:
![程序退出](/img/assets/67/01.png)




#### addShutdownHook的使用场景

addShutdownHook的使用场景很多, 尤其是在通信的模块功能,比如Netty为例,当服务端或者客户端异常断开的时候,需要告诉对方释放资源,从而保证不会触发异常机制。

例如分布式队列,RPC框架都需要在异常后,释放相关资源。


#### addShutdownHook使用注意几点

1. 在多次使用addShutdownHook增加退出的方法是,由于是开一个线程,所以不能保证方法的执行是按顺序的。 所以尽量保证方法在一个方法里面完成。

2. 在释放的时候不应该增加阻塞的方法,否者会导致推出时阻塞,导致无法退出

``` java
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
while (true) {
//
}
}));

hooks相关源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
 public void addShutdownHook(Thread hook) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(new RuntimePermission("shutdownHooks"));
}
ApplicationShutdownHooks.add(hook);
}

public boolean removeShutdownHook(Thread hook) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(new RuntimePermission("shutdownHooks"));
}
return ApplicationShutdownHooks.remove(hook);
}

```

``` java

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) {
}
}
}
}
}


```

#### Hooks管理问题

在很多开源的框架中,有的Hooks反而会起到反作用,我们怎么样能够移除我们不需要的Hooks呢?

在看了上面的代码,我们发现`ApplicationShutdownHooks` 不是一个`public`的类,JDK也没有提供对外管理的方法,只有`add`和`remove`方法。

通过分析`ApplicationShutdownHooks`类源码的结构,我们可以发现,用来存放hooks的文件的是一个`IdentityHashMap`容器,而且是一个静态的属性。

**从反射入手**

既然是一个变量,可以通过反射来获得对应的Hooks的值,代码如下:

``` java
String className = "java.lang.ApplicationShutdownHooks";
Class<?> clazz = Class.forName(className);
Field field = clazz.getDeclaredField("hooks");
field.setAccessible(true);
IdentityHashMap<Thread, Thread> map = (IdentityHashMap<Thread, Thread>) field.get(clazz);
for (Thread thread : map.keySet()) {
RzLogger.info("found shutdownHook: " + thread.getName());
}
```

增加三个Hook验证:

``` java

Runtime.getRuntime().addShutdownHook(new Thread(AppMain::exit01));
Runtime.getRuntime().addShutdownHook(new Thread(AppMain::exit02));
Runtime.getRuntime().addShutdownHook(new Thread(AppMain::exit03));


```

``` cte

23:33:11,102 INFO [main] (RzLogger.java:12) - found shutdownHook: Thread-2
23:33:11,106 INFO [main] (RzLogger.java:12) - found shutdownHook: Thread-0
23:33:11,106 INFO [main] (RzLogger.java:12) - found shutdownHook: Thread-1
exit03
exit01
exit02

收获

  1. ShutDown的使用,在程序退出后清理现场
  2. 一步一步去理解代码的原理和特性,细节很重要

一个有效的收拾程序运行残局的方法--ShutdownHook

http://blog.laofu.online/2019/11/10/2019-11-11-addShutdownHook/

作者

付威

发布于

2019-11-10

更新于

2020-08-10

许可协议

评论