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

Java中主线程终止后如何主动停止ScheduledExecutorService等子线程

问题:REST控制器提前返回响应后子线程的自主停止实现

我有一个REST控制器,它会创建两个线程:一个是定时调度线程,每隔固定周期查询数据库判断用户是否触发了退出标识;另一个是业务执行线程,我希望控制器可以直接给客户端返回200成功响应码。

原来的实现是主线程循环检测子线程状态,代码如下:

public boolean foo() {
    //flag whether is exited by User
    Boolean exited = false;
    //create a scheduler
    ScheduledExecutorService schedulService = Executors.newScheduledThreadPool(1);
    //It is worked each 20 second
    schedulService.scheduleWithFixedDelay(new FooSchdlue(exited), 0, 2000,TimeUnit.MILLISECONDS);
    //create a executor
    ExecutorService executor = Executors.newSingleThreadExecutor();
    Future<?> futrue = executor.submit(new FooExecutor());
    
    // I want this Because this is not stopped
    // Therefore I cant't return true until process is done
    while (!exited && !futrue.isDone()) {
        
    }
    
    //All thread is exited
    schedulService.shutdownNow();
    futrue.cancel(true);
    
    return true;
}

这个实现必须等两个子线程全部执行完毕后,主线程才能返回true,无法满足提前返回响应的需求,因此需要注释掉主线程的循环等待逻辑:

// I want this Because this is not stopped
    // Therefore I cant't return true until process is done
    //while (!exited && !futrue.isDone()) {
        
    //}

    //All thread is exited
    //schedulService.shutdownNow();
    //futrue.cancel(true);

预期需求是:不依赖主线程来执行停止操作,当定时调度线程检测到用户退出标识时,主动调用shutdownNow或者cancel方法停止另一个子线程,预期的定时任务实现逻辑如下:

class FooSchdlue implements Runnable{

Boolean exited = false;

public FooSchdlue(Boolean exited) {
    this.exited = exited;
}

@Override
public void run() {
        // Database check
        if(foo.getExitFlag() == true) {
            exited = true;
            ***exit Another sub thread*** 
        }
    }
}

实现方案

核心修改点

  1. Java的Boolean是不可变类,外部传入的标记修改后无法被其他线程感知,直接给定时任务类传入需要控制的ScheduledExecutorService调度器实例、业务线程的Future实例即可
  2. 额外增加业务线程正常结束后的调度器资源释放逻辑,避免线程池泄露

修改后完整代码

控制器方法

public boolean foo() {
    // 创建定时调度器
    ScheduledExecutorService schedulService = Executors.newScheduledThreadPool(1);
    // 创建业务线程池
    ExecutorService executor = Executors.newSingleThreadExecutor();
    Future<?> businessFuture = executor.submit(new FooExecutor());
    
    // 给定时任务传入需要控制的调度器和业务线程Future
    FooSchdlue scheduleTask = new FooSchdlue(schedulService, businessFuture);
    // 启动定时任务,每2秒执行一次
    schedulService.scheduleWithFixedDelay(scheduleTask, 0, 2000, TimeUnit.MILLISECONDS);
    
    // 直接返回响应,不等待子线程执行
    return true;
}

定时任务类

class FooSchdlue implements Runnable{
    private final ScheduledExecutorService scheduler;
    private final Future<?> businessFuture;

    public FooSchdlue(ScheduledExecutorService scheduler, Future<?> businessFuture) {
        this.scheduler = scheduler;
        this.businessFuture = businessFuture;
    }

    @Override
    public void run() {
        // 业务线程已经正常结束,直接关闭调度器释放资源
        if (businessFuture.isDone()) {
            scheduler.shutdownNow();
            return;
        }
        // 数据库查询用户退出标识
        if(foo.getExitFlag()) {
            // 停止业务线程,参数true代表中断正在运行的线程
            businessFuture.cancel(true);
            // 关闭调度器,停止后续定时任务执行
            scheduler.shutdownNow();
        }
    }
}

注意事项

  • 业务执行线程FooExecutor需要支持中断响应,内部要处理InterruptedException,或者在循环逻辑中定期检查Thread.currentThread().isInterrupted(),否则cancel(true)可能无法立即停止正在运行的业务线程
  • 生产环境建议使用全局共享的线程池,不要每次请求都新建线程池,避免请求量过高时线程资源耗尽
  • 如果需要记录流程状态,可以在线程关闭逻辑后补充日志或数据库落库操作

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.01 17:36:03