前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >哦,这就是java的优雅停机?(实现及原理)

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

作者头像
用户1655470
发布于 2018-12-06 09:36:03
发布于 2018-12-06 09:36:03
2K00
代码可运行
举报
文章被收录于专栏:chenssychenssy
运行总次数:0
代码可运行

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

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

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

来个栗子:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class ShutdownGracefulTest {    /**     * 使用线程池处理任务     */    public static ExecutorService executorService = Executors.newCachedThreadPool();    public static void main(String[] args) {        //假设有5个线程需要执行任务        for(int i = 0; i < 5; i++){            final int id = i;            Thread taski = new Thread(new Runnable() {                @Override                public void run() {                    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(new Runnable() {            @Override            public void run() {                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(new Runnable() {            @Override            public void run() {                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);    }}

运行结果如下:

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

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

kill -15 pid

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

  1. 关闭 socket 链接
  2. 清理临时文件
  3. 发送消息通知给订阅方,告知自己下线
  4. 将自己将要被销毁的消息通知给子进程
  5. 各种资源的释放 ...

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

kill -9 pid

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

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

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

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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); // 添加钩子,开启优雅之路

// 具体流程如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    /**     * Registers a new virtual-machine shutdown hook.     *     * @param   hook     *          An initialized but unstarted <tt>{@link Thread}</tt> object     *     * @throws  IllegalArgumentException     *          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     *     * @throws  IllegalStateException     *          If the virtual machine is already in the process     *          of shutting down     *     * @throws  SecurityException     *          If a security manager is present and it denies     *          <tt>{@link RuntimePermission}("shutdownHooks")</tt>     *     * @see #removeShutdownHook     * @see #halt(int)     * @see #exit(int)     * @since 1.3     */    public void addShutdownHook(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 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 以map类型保存, k->k 形式存储,保证每一个钩子都是独立的        hooks.put(hook, hook);    }    // java.lang.ApplicationShutdownHooks 会先注册一个静态块,添加一个任务到 Shutdown 中    /* 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() {                        // 即当该任务被调用时,调用自身的运行方法,使所有注册的 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 void runHooks() {        Collection<Thread> threads;        synchronized(ApplicationShutdownHooks.class) {            threads = hooks.keySet();            hooks = null;        }        for (Thread hook : threads) {            hook.start();        }        for (Thread hook : threads) {            try {                // 阻塞等待所有完成                hook.join();            } catch (InterruptedException x) { }        }    }

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    // 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 void shutdown() {        synchronized (lock) {            switch (state) {            case RUNNING:       /* Initiate shutdown */                state = HOOKS;                break;            case HOOKS:         /* Stall and then return */            case FINALIZERS:                break;            }        }        synchronized (Shutdown.class) {            // 执行序列            sequence();        }    }    // 而 sequence() 则会调用 runHooks(), 调用自定义的钩子任务    private static void sequence() {        synchronized (lock) {            /* Guard against the possibility of a daemon thread invoking exit             * after DestroyJavaVM initiates the shutdown sequence             */            if (state != HOOKS) return;        }        runHooks();        boolean rfoe;        synchronized (lock) {            state = FINALIZERS;            rfoe = runFinalizersOnExit;        }        if (rfoe) runAllFinalizers();    }    // 执行钩子,此处最多允许注册 10 个钩子,且进行同步调用,当然这是最顶级的钩子,钩子下还可以添加钩子,可以任意添加n个    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];                }                // 同步调用注册的hook, 即 前面看到 ApplicationShutdownHooks.runHooks()                if (hook != null) hook.run();            } catch(Throwable t) {                if (t instanceof ThreadDeath) {                    ThreadDeath td = (ThreadDeath)t;                    throw td;                }            }        }    }

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

简化为:

  1. 注册流程(应用主动调用):Runtime.addShutdownHook -> ApplicationShutdownHooks.add()/static -> java.lang.Shutdown.add()/shutdown()
  2. 执行流程(jvm自动调用):java.lang.Shutdown.shutdown()->sequence()->runHooks() -> ApplicationShutdownHooks.runHooks() -> hooks 最终

出处:https://www.cnblogs.com/yougewe/p/9881874.html

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2018-11-05,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Java技术驿站 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
8种垂直居中的方法
方法6 absolute+margin:auto 和方法5类似,当宽度和高度未知时使用
全栈程序员站长
2022/07/21
2520
多个CSS 居中方案,你可能还不知道!水平居中垂直居中水平垂直居中
最近开源了一个 Vue 组件,还不够完善,欢迎大家来一起完善它,也希望大家能给个 star 支持一下,谢谢各位了。
前端小智@大迁世界
2021/01/18
3.1K0
用CSS实现居中的总结
行内或者行内块元素居中 1.单行竖直居中 给行内元素添加上下相同大小的padding值即可 设置元素的line-height等于父容器的高度,也可以竖直居中 使用弹性盒子Flex后,设置align-items为center 涉及到图片文字混排竖直居中使用vertical-align,可以使用伪元素加入以下代码 container::after{ content:''; display:inline; vertical-align:middle; } 2.多行文字竖直居中 设置padd
卡少
2018/05/16
7520
margin:auto实现水平垂直居中
但,这种方法有一个很明显的不足,就是需要提前知道元素的尺寸。否则margin负值的调整无法精确。此时,往往要借助JS获得。
javascript.shop
2019/09/04
2.2K0
实现div里的img图片水平垂直居中
方法一: 将display设置成table-cell,然后水平居中设置text-align为center,垂直居中设置vertical-align为middle。
全栈程序员站长
2022/08/22
3.4K0
实现div里的img图片水平垂直居中
div盒子水平垂直居中方法
这是最简单的方法,不仅能实现绝对居中同样的效果,也支持联合可变高度方式使用。内容块定义transform: translate(-50%,-50%)  必须加上
Daotin
2018/08/31
2K0
第212天:15种CSS居中的方式,最全了
CSS居中是前端工程师经常要面对的问题,也是基本技能之一。今天有时间把CSS居中的方案整理了一下,目前包括水平居中,垂直居中及水平垂直居中方案共15种。如有漏掉的,还会陆续的补充进来,算做是一个备忘录吧。
半指温柔乐
2018/09/11
6430
CSS完成元素水平垂直居中
首先这个元素和它的父元素都要设置定位,其中这个要水平垂直居中的元素需设置绝对定位absolute, 然后再给它设置样式{left: 0;right: 0;top: 0;bottom: 0;margin:auto;}。这样便可以实现元素在父容器里垂直居中显示了。
colezhou
2019/11/24
1.5K0
前端开发中各类型居中方式总结
前端开发中经常用到的就是元素居中,有时候不同的元素居中方式不同就忘记了,明明已经设置了居中,但却没有效果,搞得人很懵逼,还得去搜索一下,所以今天总结了一下,方便以后查用。
岳泽以
2022/10/26
6290
前端开发中各类型居中方式总结
【前端攻略】最全面的水平垂直居中方案与flexbox布局
最近又遇到许多垂直居中的问题,这是Css布局当中十分常见的一个问题,诸如定长定宽或不定长宽的各类容器的垂直居中,其实都有很多种解决方案。而且在Css3的flexbox出现之后,解决各类居中问题变得更加容易了。搜了搜园子内关于flexbox的文章觉得很多不够详尽,故想借介绍flexbox的同时好好总结一番各类垂直居中的方法。由简至繁: 行内元素的水平居中     要实现行内元素(<span>、<a>等)的水平居中,只需把行内元素包裹在块级父层元素(<div>、<li>、<p>等)中,并且在父层元素CSS设置如
Sb_Coco
2018/05/28
1.4K0
水平垂直居中的六种方法
绝对定位方法:不确定当前div的宽度和高度,采用 transform: translate(-50%,-50%); 当前div的父级添加相对定位(position: relative;)
青梅煮码
2023/01/16
4240
水平垂直居中的六种方法
CSS中关于元素居中的方法总结(超全)
css中一个很重要的问题,就是关于元素的居中,包括水平居中,垂直居中,本文就是为大家总结了:css中对于行内元素与块级元素不同的居中方法.
用户9914333
2022/07/21
3.4K0
CSS 基础系列:水平垂直居中方案
比较全面的水平垂直居中方案。水平垂直居中问题大体分为两类,一类目标元素是行内元素,一类目标元素是块级元素(其中,块级元素又包括定宽高和不定宽高)。
Chor
2019/11/07
1.2K0
实现HTML元素垂直居中的六种方法
一、 img的垂直水平居中 使用到的重要样式属性display,vertical-align vertical-align:middle这个属性是对table元素垂直居中起作用,如果想使用在img元素上,就注意下面的display设置 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> <style type="text/css"> .mai
潇洒哥和黑大帅
2018/10/23
3.5K0
水平居中和垂直居中
本章介绍几种常见的水平居中和垂直居中的实现方式 <!DOCTYPE html> <html> <head> <title>水平居中和垂直居中</title> <meta charset="utf-8"> </head> <style type="text/css"> .box { /* 在一个基础的盒子里面显示效果 */ position: relative; float: left; width: 250px; h
echobingo
2018/04/25
2.9K0
水平居中和垂直居中
CSS实现水平垂直居中的1010种方式(史上最全)
要实现上图的效果看似很简单,实则暗藏玄机,本文总结了一下CSS实现水平垂直居中的方式大概有下面这些,本文将逐一介绍一下,我将本文整理成了一个github仓库
全栈程序员站长
2022/06/29
1K0
如何实现水平垂直居中如何实现一个块级元素的水平垂直居中
详情查看https://www.runoob.com/w3cnote/flex- grammar.html
前端小tips
2021/11/27
1.3K0
如何实现水平垂直居中如何实现一个块级元素的水平垂直居中
CSS 水平居中与垂直居中的16个方法
元素高度可以动态改变, 不需再CSS中定义, 如果父元素没有足够空间时, 该元素内容也不会被截断。
切图仔
2022/09/08
7280
HTML 水平居中 垂直居中 垂直水平居中的几种实现方式「建议收藏」
大家好,我是架构君,一个会写代码吟诗的架构师。今天说一说HTML 水平居中 垂直居中 垂直水平居中的几种实现方式「建议收藏」,希望能够帮助大家进步!!!
Java架构师必看
2022/05/22
6.1K0
HTML 水平居中 垂直居中 垂直水平居中的几种实现方式「建议收藏」
HTML & CSS页面布局之定位
默认情况下,HTML元素都在标准流中呈现和展示。我们之前把元素分为块级元素,行内元素,行内块级元素,他们的特性是块级独占一行,行内和行内块级可以在一行内共存,这些特性都是针对标准流的。总结一下就是,标准流中元素只能在水平或垂直方向上排版。如果元素是块级元素, 那么就会垂直排版,如果元素是行内元素/行内块级元素, 那么就会水平排版。
用户5997198
2020/05/12
5.7K0
推荐阅读
相关推荐
8种垂直居中的方法
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验