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

多线程下Class.forName()导致进程终止的原因及解决方法

问题分析与解决方案

首先,咱们先定位核心问题:你的进程在Class.forName(driver)处无声终止,只打印了"Before"却没有"After",也没有异常输出,这大概率是因为静态代码块中抛出了未被捕获的Error而非Exception——你的catch块只处理了IOException和ClassNotFoundException,但像NoClassDefFoundError、UnsatisfiedLinkError这类Error不属于Exception的子类,不会被捕获,JVM遇到未处理的Error会直接终止进程,自然不会打印堆栈信息。

结合你的场景(单线程正常、多线程出问题),再加上代码里的细节,我整理了几个可能的原因和对应的解决办法:


1. 先捕获所有Throwable,定位具体错误

第一步必须先搞清楚到底出了什么错。修改ConnectionUtils的静态代码块catch逻辑,把捕获范围扩大到Throwable,这样不管是Exception还是Error都能被捕获并打印:

static {
    try {
        Properties properties = new Properties();
        // 这里后面还要改资源加载方式,先解决错误捕获问题
        properties.load(new FileReader("src/main/resources/jdbcConnection.properties"));
        driver = properties.getProperty("driver");
        url = properties.getProperty("url");
        username = properties.getProperty("username");
        password = properties.getProperty("password");
        System.out.println("Before");
        Class.forName(driver);
        System.out.println("After");
    } catch (Throwable e) {
        // 打印完整的错误堆栈,包括Error类型
        e.printStackTrace();
        // 主动抛出RuntimeException,确保进程终止前能看到明确的错误信息
        throw new RuntimeException("Failed to initialize ConnectionUtils", e);
    }
}

运行后你就能看到具体的错误原因了,比如驱动类找不到、依赖缺失或者资源加载失败。


2. 修复资源文件加载方式(避免多线程/打包后路径问题)

你用FileReader读取资源文件的方式有问题:src/main/resources在IDE里是本地路径,但项目打包成JAR后,这个路径就不存在了,而且多线程环境下类加载路径可能有差异,导致单线程能读到,多线程读不到(或者加载时机不对)。

改成用类加载器读取资源流,这才是标准的资源加载方式:

// 替换原来的FileReader加载逻辑
properties.load(ConnectionUtils.class.getClassLoader().getResourceAsStream("jdbcConnection.properties"));

这样不管是IDE运行还是JAR包运行,都能正确读取到properties文件。


3. 检查JDBC驱动的正确性与依赖

如果捕获到NoClassDefFoundError,那就是驱动类没找到:

  • 确认jdbcConnection.properties里的driver值正确:比如MySQL 8.0+要用com.mysql.cj.jdbc.Driver,而不是旧的com.mysql.jdbc.Driver;Oracle的话是oracle.jdbc.driver.OracleDriver
  • 确认项目依赖里已经包含了对应的JDBC驱动包:比如Maven项目要在pom.xml里添加mysql-connector-java的依赖,版本要和数据库版本匹配
  • 如果是Java模块化项目(JDK 9+),需要在module-info.java里声明对驱动模块的依赖,比如requires mysql.connector.java;

4. 修复ConnectionUtils的线程安全设计

你的ConnectionUtils里同时有static Connection connection和ThreadLocal<Connection> t,这是矛盾的设计!静态Connection是全局共享的,多线程下会导致连接混乱,而ThreadLocal是为了每个线程独占一个连接,两者不能共存。

正确的做法是只保留ThreadLocal来管理连接,同时确保每个线程使用完连接后关闭并移除ThreadLocal中的引用,避免内存泄漏:

public class ConnectionUtils {
    static private String url;
    static private String driver;
    static private String username;
    static private String password;
    private static ThreadLocal<Connection> t = new ThreadLocal<>();

    static {
        try {
            Properties properties = new Properties();
            properties.load(ConnectionUtils.class.getClassLoader().getResourceAsStream("jdbcConnection.properties"));
            driver = properties.getProperty("driver");
            url = properties.getProperty("url");
            username = properties.getProperty("username");
            password = properties.getProperty("password");
            Class.forName(driver);
        } catch (Throwable e) {
            e.printStackTrace();
            throw new RuntimeException("Failed to initialize ConnectionUtils", e);
        }
    }

    // 获取连接:从ThreadLocal拿,没有则创建
    public static Connection getConnection() throws SQLException {
        Connection conn = t.get();
        if (conn == null || conn.isClosed()) {
            conn = DriverManager.getConnection(url, username, password);
            t.set(conn);
        }
        return conn;
    }

    // 关闭连接:从ThreadLocal移除并关闭
    public static void closeConnection() {
        Connection conn = t.get();
        if (conn != null) {
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            } finally {
                t.remove(); // 必须移除,避免ThreadLocal内存泄漏
            }
        }
    }
}

5. 完善事务处理的异常回滚逻辑

你的testTransaction()方法在catch块里只打印异常,没有回滚事务,这会导致部分执行的SQL留在数据库连接中,可能引发后续问题。修改catch块添加回滚:

private void testTransaction() {
    Connection connection = null;
    try {
        connection = ConnectionUtils.getConnection();
        connection.setAutoCommit(false);
        // 执行SQL
        Product product = new Product(1, 4000d);
        productService.updateProduct(connection, product);
        Log log = new Log(true, "None");
        logService.insertLog(connection, log);
        // 提交事务
        connection.commit();
    } catch (Exception e) {
        e.printStackTrace();
        // 异常时回滚事务
        if (connection != null) {
            try {
                connection.rollback();
            } catch (SQLException ex) {
                ex.printStackTrace();
            }
        }
    } finally {
        ConnectionUtils.closeConnection();
    }
}

按这个步骤排查和修改,应该就能解决进程无声终止的问题了。

内容的提问来源于stack exchange,提问作者sh-zh-7

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.08 23:12:32