java halt_哦,这就是java的优雅停机?(实现及原理)

优雅停机? 这个名词我是服的,如果抛开专业不谈,多好的名词啊!

其实优雅停机,就是在要关闭服务之前,不是立马全部关停,而是做好一些善后操作,比如:关闭线程、释放连接资源等。

再比如,就是不会让调用方的请求处理了一增,一下就中断了。而处理完本次后,再停止服务。

Java语言中,我们可以通过Runtime.getRuntime().addShutdownHook()方法来注册钩子,以保证程序平滑退出。(其他语言也类似)

来个栗子:

public classShutdownGracefulTest {/*** 使用线程池处理任务*/

public static ExecutorService executorService =Executors.newCachedThreadPool();public static voidmain(String[] args) {//假设有5个线程需要执行任务

for(int i = 0; i < 5; i++){final int id =i;

Thread taski= new Thread(newRunnable() {

@Overridepublic voidrun() {

System.out.println(System.currentTimeMillis()+ " : thread_" + id + " start...");try{

TimeUnit.SECONDS.sleep(id);

}catch(InterruptedException e) {

e.printStackTrace();

}

System.out.println(System.currentTimeMillis()+ " : thread_" + id + " finish!");

}

});

taski.setDaemon(true);

executorService.submit(taski);

}

Runtime.getRuntime().addShutdownHook(new Thread(newRunnable() {

@Overridepublic voidrun() {

System.out.println(System.currentTimeMillis()+ " : " + Thread.currentThread().getName() + " No1 shutdown hooking...");boolean shutdown = true;try{

executorService.shutdown();

System.out.println(System.currentTimeMillis()+ " : " + Thread.currentThread().getName() + " shutdown signal got, wait threadPool finish.");

executorService.awaitTermination(1500, TimeUnit.SECONDS);boolean done = false;

System.out.println(System.currentTimeMillis()+ " : " + Thread.currentThread().getName() + " all thread's done.");

}catch(InterruptedException e) {

e.printStackTrace();//尝试再次关闭

if(!executorService.isTerminated()) {

executorService.shutdownNow();

}

}

System.out.println(System.currentTimeMillis()+ " : " + Thread.currentThread().getName() + " No1 shutdown done...");

}

}));

Runtime.getRuntime().addShutdownHook(new Thread(newRunnable() {

@Overridepublic voidrun() {try{

System.out.println(System.currentTimeMillis()+ " : " + Thread.currentThread().getName() + " No2 shutdown hooking...");

Thread.sleep(1000);

}catch(InterruptedException e) {

e.printStackTrace();

}

System.out.println(System.currentTimeMillis()+ " : " + Thread.currentThread().getName() + " No2 shutdown done...");

}

}));

System.out.println("main method exit...");

System.exit(0);

}

}

运行结果如下:

b05fa5f7eaba885a9b4b844ef816bd5e.png

很明显,确实是优雅了,虽然最后收到了一关闭信号,但是仍然保证了任务的处理完成。很棒吧!

那么,在实际应用中是如何体现优雅停机呢?

kill -15 pid

通过该命令发送一个关闭信号给到jvm, 然后就开始执行 Shutdown Hook 了,你可以做很多:

1. 关闭 socket 链接

2. 清理临时文件

3. 发送消息通知给订阅方,告知自己下线

4. 将自己将要被销毁的消息通知给子进程

5. 各种资源的释放

...

而在平时工作中,我们不乏看到很多运维同学,是这么干的:

kill -9 pid

如果这么干的话,jvm也无法了,kill -9 相当于一次系统宕机,系统断电。这会给应用杀了个措手不及,没有留给应用任何反应的机会。

所以,无论如何是优雅不起来了。

要优雅,是代码和运维的结合!

其中,线程池的关闭方式为:

executorService.shutdown();

executorService.awaitTermination(1500, TimeUnit.SECONDS);

ThreadPoolExecutor 在 shutdown 之后会变成 SHUTDOWN 状态,无法接受新的任务,随后等待正在执行的任务执行完成。意味着,shutdown 只是发出一个命令,至于有没有关闭还是得看线程自己。

ThreadPoolExecutor 对于 shutdownNow 的处理则不太一样,方法执行之后变成 STOP 状态,并对执行中的线程调用 Thread.interrupt() 方法(但如果线程未处理中断,则不会有任何事发生),所以并不代表“立刻关闭”。

shutdown() :启动顺序关闭,其中执行先前提交的任务,但不接受新任务。如果已经关闭,则调用没有附加效果。此方法不等待先前提交的任务完成执行。

shutdownNow():尝试停止所有正在执行的任务,停止等待任务的处理,并返回正在等待执行的任务的列表。当从此方法返回时,这些任务将从任务队列中耗尽(删除)。此方法不等待主动执行的任务终止。

executor.awaitTermination(this.awaitTerminationSeconds, TimeUnit.SECONDS)); 控制等待的时间,防止任务无限期的运行(前面已经强调过了,即使是 shutdownNow 也不能保证线程一定停止运行)。

注意:

虚拟机会对多个shutdownhook以未知的顺序调用,都执行完后再退出。

如果接收到 kill -15 pid 命令时,执行阻塞操作,可以做到等待任务执行完成之后再关闭 JVM。同时,也解释了一些应用执行 kill -15 pid 无法退出的问题,如:中断被阻塞了,或者hook运行了死循环代码。

实现原理:

Runtime.getRuntime().addShutdownHook(hook); //添加钩子,开启优雅之路

// 具体流程如下:

/*** Registers a new virtual-machine shutdown hook.

*

*@paramhook

* An initialized but unstarted {@linkThread} object

*

*@throwsIllegalArgumentException

* If the specified hook has already been registered,

* or if it can be determined that the hook is already running or

* has already been run

*

*@throwsIllegalStateException

* If the virtual machine is already in the process

* of shutting down

*

*@throwsSecurityException

* If a security manager is present and it denies

* {@linkRuntimePermission}("shutdownHooks")

*

*@see#removeShutdownHook

*@see#halt(int)

*@see#exit(int)

*@since1.3*/

public voidaddShutdownHook(Thread hook) {

SecurityManager sm=System.getSecurityManager();if (sm != null) {

sm.checkPermission(new RuntimePermission("shutdownHooks"));

}//添加到 application 中

ApplicationShutdownHooks.add(hook);

}//java.lang.ApplicationShutdownHooks.add(hook);

static synchronized voidadd(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 以map类型保存, k->k 形式存储,保证每一个钩子都是独立的

hooks.put(hook, hook);

}//java.lang.ApplicationShutdownHooks 会先注册一个静态块,添加一个任务到 Shutdown 中

/*The set of registered hooks*/

private static IdentityHashMaphooks;static{try{

Shutdown.add(1 /*shutdown hook invocation order*/,false /*not registered if shutdown in progress*/,newRunnable() {public voidrun() {//即当该任务被调用时,调用自身的运行方法,使所有注册的 hook 运行起来

runHooks();

}

}

);

hooks= new IdentityHashMap<>();

}catch(IllegalStateException e) {//application shutdown hooks cannot be added if//shutdown is in progress.

hooks = null;

}

}//runHooks 执行所有钩子线程,进行异步调用

/*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 voidrunHooks() {

Collectionthreads;synchronized(ApplicationShutdownHooks.class) {

threads=hooks.keySet();

hooks= null;

}for(Thread hook : threads) {

hook.start();

}for(Thread hook : threads) {try{//阻塞等待所有完成

hook.join();

}catch(InterruptedException x) { }

}

}

到现在为止,我们已经知道关闭钩子是如何执行的,但是,还不是知道,该钩子是何时触发?

//java.lang.Shutdown.add() 该方法会jvm主动调用,从而触发 后续钩子执行

/*Invoked by the JNI DestroyJavaVM procedure when the last non-daemon

* thread has finished. Unlike the exit method, this method does not

* actually halt the VM.*/

static voidshutdown() {synchronized(lock) {switch(state) {case RUNNING: /*Initiate shutdown*/state=HOOKS;break;case HOOKS: /*Stall and then return*/

caseFINALIZERS:break;

}

}synchronized (Shutdown.class) {//执行序列

sequence();

}

}//而 sequence() 则会调用 runHooks(), 调用自定义的钩子任务

private static voidsequence() {synchronized(lock) {/*Guard against the possibility of a daemon thread invoking exit

* after DestroyJavaVM initiates the shutdown sequence*/

if (state != HOOKS) return;

}

runHooks();booleanrfoe;synchronized(lock) {

state=FINALIZERS;

rfoe=runFinalizersOnExit;

}if(rfoe) runAllFinalizers();

}//执行钩子,此处最多允许注册 10 个钩子,且进行同步调用,当然这是最顶级的钩子,钩子下还可以添加钩子,可以任意添加n个

private static voidrunHooks() {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];

}//同步调用注册的hook, 即 前面看到 ApplicationShutdownHooks.runHooks()

if (hook != null) hook.run();

}catch(Throwable t) {if (t instanceofThreadDeath) {

ThreadDeath td=(ThreadDeath)t;throwtd;

}

}

}

}

如此,整个关闭流程完美了。

简化为:

1. 注册流程(应用主动调用)

Runtime.addShutdownHook -> ApplicationShutdownHooks.add()/static -> java.lang.Shutdown.add()/shutdown()

2. 执行流程(jvm自动调用)

java.lang.Shutdown.shutdown()->sequence()->runHooks() -> ApplicationShutdownHooks.runHooks() -> hooks 最终

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值