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

Spring Boot中ExecutorService关闭时记录未完成任务ID的方案

优雅停机记录未完成任务ID的实现方案

针对你的场景(任务不可中断、阻塞等待外部服务回复),方案二(跟踪任务生命周期)是更可靠的选择,方案一因任务不可中断存在局限性,下面分别说明:

方案一:包装任务跟踪中断状态(局限性说明)

由于你的任务是不可中断的阻塞操作,shutdownNow()仅会给线程设置中断标记,但任务本身不会停止执行。这个方案只能在任务被标记中断后记录ID,但无法终止任务,仅能满足记录需求:

@Service
public class TaskService {

    private ExecutorService es = Executors.newFixedThreadPool(10);
    private final Set<TaskWrapper> activeTasks = Collections.synchronizedSet(new HashSet<>());

    public void handle(MyTask task) {
        TaskWrapper wrapper = new TaskWrapper(task);
        activeTasks.add(wrapper);
        es.execute(wrapper);
    }

    @PreDestroy
    void destroy() {
        es.shutdown();
        try {
            boolean terminated = es.awaitTermination(60, TimeUnit.SECONDS);
            if (!terminated) {
                // 输出所有未完成的任务ID
                activeTasks.forEach(w -> System.out.println("未完成任务ID: " + w.getTaskId()));
                es.shutdownNow();
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }

    private class TaskWrapper implements Runnable {
        private final MyTask task;

        public TaskWrapper(MyTask task) {
            this.task = task;
        }

        public String getTaskId() {
            return task.getId(); // 假设MyTask提供getId()方法
        }

        @Override
        public void run() {
            try {
                task.run();
            } finally {
                activeTasks.remove(this);
            }
        }
    }
}

方案二:线程安全集合跟踪任务生命周期(推荐)

核心是用线程安全集合记录所有提交的任务,任务完成(无论成功/失败)后自动移除,停机时直接遍历集合获取未完成任务ID,完全适配你的不可中断任务场景:

@Service
public class TaskService {

    private ExecutorService es = Executors.newFixedThreadPool(10);
    // 用ConcurrentHashMap高效跟踪活跃任务,键为任务ID
    private final ConcurrentMap<String, MyTask> activeTasks = new ConcurrentHashMap<>();

    public void handle(MyTask task) {
        String taskId = task.getId();
        activeTasks.put(taskId, task);
        es.execute(() -> {
            try {
                task.run();
            } catch (Exception e) {
                System.err.println("任务执行异常,ID: " + taskId);
            } finally {
                // 无论任务结果如何,执行完成后从集合移除
                activeTasks.remove(taskId);
            }
        });
    }

    @PreDestroy
    void destroy() {
        es.shutdown();
        try {
            if (!es.awaitTermination(60, TimeUnit.SECONDS)) {
                // 打印所有超时未完成的任务ID
                System.out.println("超时未完成任务ID列表:");
                activeTasks.keySet().forEach(id -> System.out.println("- " + id));
                // 调用shutdownNow标记线程状态,虽无法中断任务,但不影响记录
                es.shutdownNow();
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            System.err.println("优雅停机过程被中断");
            es.shutdownNow();
        }
    }
}

关键注意事项

  • 必须在finally块中移除任务,避免因任务抛出异常导致任务长期留在集合中
  • ConcurrentHashMap比同步集合(如Collections.synchronizedSet)性能更优,适合高并发场景
  • 由于任务不可中断,应用超时关闭后JVM会强制终止这些任务,因此记录ID是后续排查的关键

内容的提问来源于stack exchange,提问作者John Allison

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.19 05:40:53