You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Java类循环依赖导致类加载阻塞问题及解决方案咨询

问题根因

你遇到的是JVM类初始化阶段的循环依赖死锁,JVM规范要求类初始化过程是线程安全的:同一个类的初始化只会被一个线程持有初始化锁,其他访问该类的线程会被阻塞直到初始化完成。
你的代码形成了循环依赖链:Misc初始化 → 调用GetDBData.execute() → 实例化GetDBData → 读取Constants.mysql → 触发Constants初始化 → 读取Misc.sql。当两个线程刚好分别触发Misc和Constants的初始化时,就会出现Misc等Constants初始化完成、Constants等Misc初始化完成的死锁情况。你遇到的SQL报错也是因为死锁时Misc.sql还未完成赋值,Constants.mysql拿到空值,拼接后的SQL语句缺失必要内容导致的。


方案解答

1. 单例改造方案

改造成本确实过高,完全不需要采用。

2. 解耦Constants和Misc

这是根治方案,如果暂时无法处理所有依赖,可以先采用临时方案兜底。

3. JSP调用迁移到Action的可行性

不能解决问题。JSP和Struts Action本质没有线程模型差异:JSP会被编译成Servlet运行,和Action一样都是Tomcat线程池调度的HTTP请求处理线程。只要并发请求分别触发Misc和Constants的初始化,死锁仍然会发生。

4. 低改造成本方案

以下方案都不需要修改原有业务调用逻辑,改造成本极低:

  • 方案1:启动时主动预热类加载
    新增一个ServletContextListener,在应用启动阶段就主动触发Misc和Constants的类初始化,用户请求进入时所有相关类已经初始化完成,完全避免运行时并发加载的死锁风险。只需要新增一个监听器类,在web.xml中注册即可,无需修改任何业务代码。
    代码示例:
    public class ClassInitListener implements ServletContextListener {
        @Override
        public void contextInitialized(ServletContextEvent sce) {
            // 主动触发类初始化
            try {
                Class.forName("Misc");
                Class.forName("Constants");
            } catch (ClassNotFoundException e) {
                throw new RuntimeException("初始化核心类失败", e);
            }
        }
    }
    
  • 方案2:修改Misc为懒加载逻辑
    把dbData的初始化逻辑从静态代码块移到getDbData()方法中,用双重检查锁实现懒加载,静态代码块仅负责sql常量的赋值,避免类初始化阶段触发依赖链。原有10000多处Misc.getDbData()调用完全不需要修改:
    public class Misc {
        public static final String sql = "select";
        private static volatile String dbData;
    
        static {
            System.out.println("static started");
            // 仅初始化常量,不触发依赖调用
            System.out.println("static ended");
        }
    
        public static String getDbData() {
            System.out.println("getDbData");
            if (dbData == null) {
                synchronized (Misc.class) {
                    if (dbData == null) {
                        dbData = GetDBData.execute();
                    }
                }
            }
            return dbData;
        }
    }
    
  • 方案3:修改Constants常量加载逻辑
    把Constants.mysql从静态常量改为静态方法返回,避免Constants初始化阶段直接读取Misc.sql,切断类初始化阶段的循环依赖:
    public class Constants {
        public static String getMysql() {
            return Misc.sql;
        }
    }
    
    只需要把原有Constants.mysql的调用改为Constants.getMysql()即可,修改范围仅局限在GetDBData类中。

内容的提问来源于stack exchange,提问作者Lucian L.

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.14 15:54:07