Runtime.getRuntime().addShutdownHook 使用实例

本文详细介绍了Java中Runtime.addShutdownHook方法的用途及工作原理。该方法允许在JVM关闭前执行特定任务,如资源清理等。文章通过示例代码展示了如何使用此功能,并解释了钩子线程的行为。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

今天看 14年 某个前辈留下的关于 JedisPool 的代码中用到了这个东西,但是不知道是干啥的,就查了一下


Runtime.addShutdownHook解释

如果你想在jvm关闭的时候进行内存清理、对象销毁等操作,或者仅仅想起个线程然后这个线程不会退出,你可以使用Runtime.addShutdownHook。

这个方法的作用就是在JVM中增加一个关闭的钩子

当程序正常退出、系统调用 System.exit方法或者虚拟机被关闭时才会执行系统中已经设置的所有钩子,当系统执行完这些钩子后,JVM才会关闭。

所谓钩子,就是一个已初始化但并不启动的线程。JVM退出通常通过两种事件。

  • 程序正常退出,例如最后一个非守护进程退出、使用System.exit()退出等
  • 程序异常退出,例如使用Ctrl+C触发的中断、用户退出或系统关闭等系统事件等 该方法的说明如下

Runtime.addShutdownHook示例

public class ShutdownHookDemo {
    public static void main(String[] args) {
        Thread clearHook = new Thread() {
            public void run() {
                System.out.println("Run clearHook...");
            }
        };
        Runtime.getRuntime().addShutdownHook(clearHook);
        Runnable task1 = new Runnable() {
            public void run() {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("Run task1...");
            }
        };
        Runnable task2 = new Thread() {
            public void run() {
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("Run task2...");
            }
        };
        task1.run();
        task2.run();
    }
}
Run task1...
Run task2...
Run clearHook...

参考资料:

1、Java中Runtime.addShutdownHook用法
http://blog.jerkybible.com/2016/10/17/Java%E4%B8%ADRuntime-addShutdownHook%E7%94%A8%E6%B3%95/

2、Runtime.getRuntime().addShutdownHook简介
https://www.jianshu.com/p/5e6dffd1776f

3、RunTime.getRunTime().addShutdownHook用法
https://blog.youkuaiyun.com/wgw335363240/article/details/5854402


public class RpcApplication { private static volatile RpcConfig rpcConfig; /** * 框架初始化,支持传入自定义配置 * * @param newRpcConfig */ public static void init(RpcConfig newRpcConfig) { rpcConfig = newRpcConfig; log.info("rpc init, config = {}", newRpcConfig.toString()); // 注册中心初始化 RegistryConfig registryConfig = rpcConfig.getRegistryConfig(); Registry registry = RegistryFactory.getInstance(registryConfig.getRegistry()); registry.init(registryConfig); log.info("registry init, config = {}", registryConfig); // 创建并注册 Shutdown Hook,JVM 退出时执行操作 Runtime.getRuntime().addShutdownHook(new Thread(registry::destroy)); } /** * 初始化 */ public static void init() { RpcConfig newRpcConfig; try { newRpcConfig = ConfigUtils.loadConfig(RpcConfig.class, RpcConstant.DEFAULT_CONFIG_PREFIX); } catch (Exception e) { // 配置加载失败,使用默认值 newRpcConfig = new RpcConfig(); } init(newRpcConfig); } /** * 获取配置 * * @return */ public static RpcConfig getRpcConfig() { if (rpcConfig == null) { synchronized (RpcApplication.class) { if (rpcConfig == null) { init(); } } } return rpcConfig; } } public class EasyProviderExample { public static void main(String[] args) { RpcApplication.init(); //注册服务 LocalRegistry.register(UserService.class.getName(), UserServiceImpl.class); // 启动 web 服务 HttpServer httpServer = new VertxHttpServer(); httpServer.doStart(8080); } } 这是我的两段代码,请问我每次开启provide的时候都进行初始化,不会破坏单例吗,不会造成getRpcConfig方法没用吗
最新发布
03-15
<think>嗯,用户的问题是关于他们提供的Java代码中RPC初始化是否会影响单例模式,尤其是担心每次启动provider时都调用RpcApplication.init()是否会破坏单例,导致getRpcConfig方法失效。我需要先仔细分析他们的代码结构,找出问题所在。 首先看RpcApplication类。里面有一个静态的volatile变量rpcConfig,这应该是单例的关键。init方法有两个重载版本,一个接受自定义配置,另一个加载默认配置。getRpcConfig方法用了双重检查锁定来确保线程安全,如果rpcConfig为null,就调用init()初始化。 接下来是EasyProviderExample的main方法,每次启动时都调用了RpcApplication.init()。这里用户担心多次初始化会不会破坏单例,导致配置被覆盖或者不一致。 我需要考虑几个点:RpcApplication.init()被多次调用的影响,单例模式在这里如何维护,以及volatile和synchronized的作用。 首先,init()方法在没有参数的情况下会加载配置,可能覆盖已有的配置。如果用户多次调用init(),那么rpcConfig会被重新赋值,这可能不是用户期望的。例如,如果第一次初始化加载了默认配置,第二次调用可能再次加载,覆盖之前的设置,导致问题。 但是,在getRpcConfig中使用了双重检查锁,确保即使多个线程同时调用,也只会初始化一次。不过,如果用户手动在外部多次调用init(),就会绕过这个锁,直接修改rpcConfig的值。这时候volatile保证了可见性,但无法阻止多次初始化。 所以,问题可能出在用户主动调用init()多次,导致配置被覆盖。比如在EasyProviderExample中,每次启动provider都调用RpcApplication.init(),如果之前已经初始化过,这会重新加载配置,可能覆盖之前的设置,破坏单例的正确性。 此外,LocalRegistry.register和启动HttpServer可能依赖RpcConfig的配置,如果配置被多次初始化,可能导致服务端口或其他参数被错误地修改。 解决方法可能是确保init()只被调用一次。可以在init方法中添加检查,如果rpcConfig已经存在,则不再执行初始化。或者将初始化逻辑移到getRpcConfig中,通过双重检查锁保证只初始化一次,而外部不应该直接调用init(),除非需要强制重新加载配置。 用户现在的情况是,每次启动provider都调用了init(),这可能强制重新加载配置,覆盖现有实例。因此,建议修改代码,只在需要时初始化一次,例如在getRpcConfig中进行延迟初始化,而避免在main中显式调用init()。或者在init方法中加入判断,如果已经初始化过,则跳过。 总结来说,用户的代码中多次调用init()会导致rpcConfig被重新赋值,破坏单例的唯一性。应该调整初始化逻辑,确保无论外部调用多少次,配置只加载一次,或者在允许重新配置的情况下处理好状态。</think>你的代码存在 **"重复初始化导致配置覆盖"** 的问题,但 **不会直接破坏单例**。以下是具体分析及解决方案: --- ### 一、问题分析 #### 1. 单例模式实现分析 在 `RpcApplication` 类中,`rpcConfig` 的初始化逻辑本质上是 **双重检查锁定(Double-Checked Locking)** 的单例模式: ```java public static RpcConfig getRpcConfig() { if (rpcConfig == null) { // 第一次检查 synchronized (RpcApplication.class) { // 加锁 if (rpcConfig == null) { // 第二次检查 init(); // 初始化 } } } return rpcConfig; } ``` - **volatile** 关键字保证 `rpcConfig` 的可见性,避免多线程下读取到过期值 - **synchronized** 保证同一时刻只有一个线程能初始化 - 双重检查确保即使多线程并发调用,初始化只会执行一次 #### 2. 问题根源 你 **在 `main` 方法中主动调用了 `RpcApplication.init()`**,这会绕过 `getRpcConfig` 中的单例控制逻辑: ```java public static void main(String[] args) { RpcApplication.init(); // 主动初始化 // ... } ``` - 每次启动 Provider 时,如果直接调用 `init()`,会导致 `rpcConfig` **被重复初始化** - 如果多次调用 `init()`,最后一次调用的配置会覆盖之前的配置 --- ### 二、具体影响 #### 1. 配置覆盖问题 假设你的 `RpcConfig` 中包含端口号、注册中心地址等关键参数: - 第一次调用 `init()` 加载配置 A(如端口 8080) - 第二次调用 `init()` 加载配置 B(如端口 9090) - 最终 `rpcConfig` 会指向配置 B,导致实际行为与预期不符 #### 2. 单例是否被破坏? - **单例对象本身未被破坏**:`rpcConfig` 始终是同一个静态变量 - **单例数据被破坏**:多次调用 `init()` 会修改 `rpcConfig` 指向的配置对象,导致配置数据不一致 --- ### 三、解决方案 #### 1. 禁止主动初始化(推荐) 将初始化逻辑完全交给 `getRpcConfig` 控制,**删除 `main` 方法中的 `RpcApplication.init()`**: ```java public static void main(String[] args) { // 直接通过 getRpcConfig() 触发初始化(单例) RpcConfig config = RpcApplication.getRpcConfig(); LocalRegistry.register(UserService.class.getName(), UserServiceImpl.class); HttpServer httpServer = new VertxHttpServer(); httpServer.doStart(8080); } ``` - 通过 `getRpcConfig()` 触发初始化,确保单例逻辑生效 - 避免手动调用 `init()` 导致重复初始化 #### 2. 增加初始化保护(可选) 在 `init()` 方法中增加状态检查,避免重复初始化: ```java public static void init() { if (rpcConfig != null) { throw new IllegalStateException("RpcConfig already initialized!"); } // 原有初始化逻辑... } ``` - 强制要求全局只能初始化一次 - 适合需要严格限制初始化次数的场景 --- ### 四、代码优化建议 #### 1. 配置加载优化 在无参 `init()` 方法中,建议区分 **首次加载** 和 **重复加载**: ```java public static void init() { if (rpcConfig != null) { log.warn("RpcConfig already initialized!"); return; } // 加载配置逻辑... } ``` #### 2. 注册中心解耦 将注册中心初始化逻辑封装到 `RpcConfig` 中,避免 `RpcApplication` 类过于臃肿: ```java public class RpcConfig { public void initRegistry() { Registry registry = RegistryFactory.getInstance(this.registryConfig.getRegistry()); registry.init(this.registryConfig); } } ``` --- ### 总结 你的代码单例模式实现正确,但 **主动调用 `init()` 绕过了单例控制**,导致配置可能被覆盖。通过将初始化逻辑交给 `getRpcConfig()` 触发,或增加初始化保护,可确保配置单例的一致性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值