MST
星途 面试题库

面试题:Java线程池关闭过程中如何优雅地处理资源释放与任务完整性

假设在一个复杂的Java应用场景中,线程池执行的任务涉及到数据库连接、文件句柄等资源的操作。请设计一个方案,在线程池关闭过程中,确保所有任务正确完成,同时合理释放这些外部资源,避免资源泄露,并分析在不同的线程池状态转换下可能面临的问题及解决方案。
49.8万 热度难度
编程语言Java

知识考点

AI 面试

面试题答案

一键面试

设计方案

  1. 自定义任务类
    • 创建一个实现Runnable接口的自定义任务类,例如ResourceTask。在这个类中封装数据库连接、文件句柄等资源的操作。
    • ResourceTask类中添加获取和释放资源的方法,例如:
public class ResourceTask implements Runnable {
    private Connection connection;
    private FileHandle fileHandle;

    public ResourceTask(Connection connection, FileHandle fileHandle) {
        this.connection = connection;
        this.fileHandle = fileHandle;
    }

    @Override
    public void run() {
        // 执行数据库和文件操作
        try {
            // 数据库操作
            Statement statement = connection.createStatement();
            ResultSet resultSet = statement.executeQuery("SELECT * FROM some_table");
            // 文件操作
            fileHandle.write("Some data");
        } catch (SQLException | IOException e) {
            e.printStackTrace();
        }
    }

    public void releaseResources() {
        if (connection != null) {
            try {
                connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (fileHandle != null) {
            try {
                fileHandle.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
  1. 线程池关闭逻辑
    • 获取线程池对象,例如ThreadPoolExecutor executor
    • 调用executor.shutdown()方法,启动有序关闭,此时线程池不再接受新任务,但会继续执行已提交的任务。
    • 使用while循环结合executor.awaitTermination方法等待所有任务执行完成:
executor.shutdown();
try {
    if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
        executor.shutdownNow();
        if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
            System.err.println("Pool did not terminate");
        }
    }
} catch (InterruptedException ie) {
    executor.shutdownNow();
    Thread.currentThread().interrupt();
}
  • 在任务执行完成后(即awaitTermination返回true),遍历线程池中的任务队列,对每个ResourceTask调用releaseResources方法释放资源:
List<Runnable> tasks = executor.getQueue();
for (Runnable task : tasks) {
    if (task instanceof ResourceTask) {
        ((ResourceTask) task).releaseResources();
    }
}

不同线程池状态转换下可能面临的问题及解决方案

  1. RUNNING状态
    • 问题:在RUNNING状态下调用shutdownshutdownNow方法时,可能会有新任务不断提交进来,导致任务队列持续增长。
    • 解决方案:在调用shutdownshutdownNow之前,通过外部控制逻辑停止新任务的提交,例如设置一个标志位,在任务提交处检查该标志位。
  2. SHUTDOWN状态
    • 问题:可能存在任务执行时间过长,导致awaitTermination超时,无法正常关闭线程池。
    • 解决方案:可以在任务设计时,合理设置任务执行的超时时间。如果任务执行时间过长,可以通过Future类的cancel方法尝试取消任务。另外,适当延长awaitTermination的等待时间,同时监控任务执行情况,避免长时间等待。
  3. STOP状态
    • 问题:调用shutdownNow进入STOP状态时,正在执行的任务可能被中断,导致资源没有正确释放。
    • 解决方案:在任务的run方法中,使用try - catch块捕获InterruptedException,在捕获到异常时,正确释放资源。例如在ResourceTaskrun方法中:
@Override
public void run() {
    try {
        // 执行数据库和文件操作
        Statement statement = connection.createStatement();
        ResultSet resultSet = statement.executeQuery("SELECT * FROM some_table");
        fileHandle.write("Some data");
    } catch (SQLException | IOException e) {
        e.printStackTrace();
    } catch (InterruptedException e) {
        releaseResources();
        Thread.currentThread().interrupt();
    }
}
  1. TERMINATED状态
    • 问题:一般情况下,进入TERMINATED状态表示所有任务已执行完成,但可能存在资源未释放的情况(如前面提到的任务执行超时等导致资源未释放)。
    • 解决方案:在进入TERMINATED状态后,再次检查并释放资源,如上述遍历任务队列释放资源的操作。同时,在应用层面添加资源监控机制,定期检查是否有未释放的资源。