Java Shutdown Hook 场景使用和源码分析,聊一聊MySQL数据库中的那些锁

运行结果

程序开始启动…

执行钩子方法…

Exception in thread “main” java.lang.ArithmeticException: / by zero

at com.chenpi.ShutdownHookDemo.main(ShutdownHookDemo.java:12)

Process finished with exit code 1

至于系统被关闭,或者使用 Kill pid 命令杀掉进程就不演示了,感兴趣的可以自行验证。

注意事项


可以向虚拟机注册多个关闭钩子,但是注意这些钩子执行是并发的,执行顺序是不确定的。

package com.chenpi;

public class ShutdownHookDemo {

static {

Runtime.getRuntime().addShutdownHook(new Thread(() -> System.out.println(“执行钩子方法A…”)));

Runtime.getRuntime().addShutdownHook(new Thread(() -> System.out.println(“执行钩子方法B…”)));

Runtime.getRuntime().addShutdownHook(new Thread(() -> System.out.println(“执行钩子方法C…”)));

}

public static void main(String[] args) throws InterruptedException {

System.out.println(“程序开始启动…”);

Thread.sleep(2000);

System.out.println(“程序即将退出…”);

}

}

运行结果

程序开始启动…

程序即将退出…

执行钩子方法B…

执行钩子方法C…

执行钩子方法A…


向虚拟机注册的钩子方法需要尽快执行结束,尽量不要执行长时间的操作,例如 I/O 等可能被阻塞的操作,死锁等,这样就会导致程序短时间不能被关闭,甚至一直关闭不了。我们也可以引入超时机制强制退出钩子,让程序正常结束。

package com.chenpi;

public class ShutdownHookDemo {

static {

Runtime.getRuntime().addShutdownHook(new Thread(() -> {

// 模拟长时间的操作

try {

Thread.sleep(1000000);

} catch (InterruptedException e) {

e.printStackTrace();

}

}));

}

public static void main(String[] args) throws InterruptedException {

System.out.println(“程序开始启动…”);

Thread.sleep(2000);

System.out.println(“程序即将退出…”);

}

}

以上的钩子执行时间比较长,最终会导致程序在等待很长时间之后才能被关闭。


如果 JVM 已经调用执行关闭钩子的过程中,不允许注册新的钩子和注销已经注册的钩子,否则会报 IllegalStateException 异常。通过源码分析,JVM 调用钩子的时候,即调用 ApplicationShutdownHooks#runHooks() 方法,会将所有钩子从变量 hooks 取出,然后将此变量置为 null

// 调用执行钩子

static void runHooks() {

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

}

}

在注册和注销钩子的方法中,首先会判断 hooks 变量是否为 null,如果为 null 则抛出异常。

// 注册钩子

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);

}

// 注销钩子

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;

}

我们演示下这种情况

package com.chenpi;

public class ShutdownHookDemo {

static {

Runtime.getRuntime().addShutdownHook(new Thread(() -> {

System.out.println(“执行钩子方法…”);

Runtime.getRuntime().addShutdownHook(new Thread(

() -> System.out.println(“在JVM调用钩子的过程中再新注册钩子,会报错IllegalStateException”)));

// 在JVM调用钩子的过程中注销钩子,会报错IllegalStateException

Runtime.getRuntime().removeShutdownHook(Thread.currentThread());

}));

}

public static void main(String[] args) throws InterruptedException {

System.out.println(“程序开始启动…”);

Thread.sleep(2000);

System.out.println(“程序即将退出…”);

}

}

运行结果

程序开始启动…

程序即将退出…

执行钩子方法…

Exception in thread “Thread-0” java.lang.IllegalStateException: Shutdown in progress

at java.lang.ApplicationShutdownHooks.add(ApplicationShutdownHooks.java:66)

at java.lang.Runtime.addShutdownHook(Runtime.java:211)

at com.chenpi.ShutdownHookDemo.lambda$static$1(ShutdownHookDemo.java:8)

at java.lang.Thread.run(Thread.java:748)


如果调用 Runtime.getRuntime().halt() 方法停止 JVM,那么虚拟机是不会调用钩子的。

package com.chenpi;

public class ShutdownHookDemo {

static {

Runtime.getRuntime().addShutdownHook(new Thread(() -> System.out.println(“执行钩子方法…”)));

}

public static void main(String[] args) {

System.out.println(“程序开始启动…”);

System.out.println(“程序即将退出…”);

Runtime.getRuntime().halt(0);

}

}

运行结果

程序开始启动…

程序即将退出…

Process finished with exit code 0

如果要想终止执行中的钩子方法,只能通过调用 Runtime.getRuntime().halt() 方法,强制让程序退出。在Linux环境中使用 kill -9 pid 命令也是可以强制终止退出。

package com.chenpi;

public class ShutdownHookDemo {

static {

Runtime.getRuntime().addShutdownHook(new Thread(() -> {

System.out.println(“开始执行钩子方法…”);

Runtime.getRuntime().halt(-1);

System.out.println(“结束执行钩子方法…”);

}));

}

public static void main(String[] args) {

System.out.println(“程序开始启动…”);

System.out.println(“程序即将退出…”);

}

}

运行结果

程序开始启动…

程序即将退出…

开始执行钩子方法…

Process finished with exit code -1


如果程序使用 Java Security Managers,使用 shutdown Hook 则需要安全权限 RuntimePermission(“shutdownHooks”),否则会导致 SecurityException

实践


例如,我们程序自定义了一个线程池,用来接收和处理任务。如果程序突然奔溃异常退出,这时线程池的所有任务有可能还未处理完成,如果不处理完程序就直接退出,可能会导致数据丢失,业务异常等重要问题。这时钩子就派上用场了。

import java.util.concurrent.ExecutorService;

import java.util.concurrent.Executors;

import java.util.concurrent.TimeUnit;

public class ShutdownHookDemo {

// 线程池

private static ExecutorService executorService = Executors.newFixedThreadPool(3);

static {

Runtime.getRuntime().addShutdownHook(new Thread(() -> {

System.out.println(“开始执行钩子方法…”);

// 关闭线程池

executorService.shutdown();

try {

// 等待60秒

System.out.println(executorService.awaitTermination(60, TimeUnit.SECONDS));

} catch (InterruptedException e) {

e.printStackTrace();

}

System.out.println(“结束执行钩子方法…”);

}));

}

public static void main(String[] args) throws InterruptedException {

System.out.println(“程序开始启动…”);

// 向线程池添加10个任务

for (int i = 0; i < 10; i++) {

Thread.sleep(1000);

final int finalI = i;

executorService.execute(() -> {

try {

Thread.sleep(4000);

} catch (InterruptedException e) {

e.printStackTrace();

}

System.out.println(“Task " + finalI + " execute…”);

});

System.out.println(“Task " + finalI + " is in thread pool…”);

}

}

}

在命令行窗口中运行程序,在10个任务都提交到线程池之后,任务都还未处理完成之前,使用 Ctrl+C 中断程序,最终在虚拟机关闭之前,调用了关闭钩子,关闭线程池,并且等待60秒让所有任务执行完成。

Shutdown Hook 在 Spring 中的运用


Shutdown Hook 在 Spring 中是如何运用的呢。通过源码分析,Springboot 项目启动时会判断 registerShutdownHook 的值是否为 true,默认是 true,如果为真则向虚拟机注册关闭钩子。

private void refreshContext(ConfigurableApplicationContext context) {

refresh(context);

if (this.registerShutdownHook) {

try {

context.registerShutdownHook();

}

catch (AccessControlException ex) {

// Not allowed in some environments.

}

}

}

@Override

public void registerShutdownHook() {

if (this.shutdownHook == null) {

// No shutdown hook registered yet.

this.shutdownHook = new Thread() {

@Override

public void run() {

synchronized (startupShutdownMonitor) {

// 钩子方法

doClose();

}

}

};

// 底层还是使用此方法注册钩子

Runtime.getRuntime().addShutdownHook(this.shutdownHook);

}

}

在关闭钩子的方法 doClose 中,会做一些虚拟机关闭前处理工作,例如销毁容器里所有单例 Bean,关闭 BeanFactory,发布关闭事件等等。

protected void doClose() {

// Check whether an actual close attempt is necessary…

if (this.active.get() && this.closed.compareAndSet(false, true)) {

if (logger.isDebugEnabled()) {

logger.debug("Closing " + this);

}

LiveBeansView.unregisterApplicationContext(this);

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注Java)
img

最后

各位读者,由于本篇幅度过长,为了避免影响阅读体验,下面我就大概概括了整理了

g-moTcOOGc-1712079578036)]
[外链图片转存中…(img-FHkiGDWw-1712079578036)]
[外链图片转存中…(img-inz3KMQ5-1712079578037)]
[外链图片转存中…(img-a16i63VJ-1712079578037)]
[外链图片转存中…(img-w4DGCFlU-1712079578037)]
[外链图片转存中…(img-TY0UcSBV-1712079578037)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注Java)
[外链图片转存中…(img-YmzxuMrO-1712079578038)]

最后

各位读者,由于本篇幅度过长,为了避免影响阅读体验,下面我就大概概括了整理了

[外链图片转存中…(img-ZIOM9EEq-1712079578038)]

[外链图片转存中…(img-0BSN01VG-1712079578038)]

[外链图片转存中…(img-xiB1Gx3A-1712079578038)]

[外链图片转存中…(img-WeWBkKQQ-1712079578039)]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值