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*** } } }
实现方案
核心修改点
- Java的
Boolean是不可变类,外部传入的标记修改后无法被其他线程感知,直接给定时任务类传入需要控制的ScheduledExecutorService调度器实例、业务线程的Future实例即可 - 额外增加业务线程正常结束后的调度器资源释放逻辑,避免线程池泄露
修改后完整代码
控制器方法
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
相关产品推荐
相关产品推荐

