凌晨3点15分,电商平台的订单处理系统正准备进行每日维护。作为系统负责人,你需要在30分钟内完成所有待处理订单,同时确保系统平稳下线。这时你发现线程池中还有2万多个待处理任务,而强行终止可能导致数据不一致。这就是我们今天要解决的优雅关闭问题。
线程池就像一家24小时营业的餐厅。打烊时,老板需要决定:是立刻赶走所有顾客(可能引发投诉),还是等现有顾客吃完但不接待新客人(可能等到天亮)。Java的ExecutorService提供了三种关键方法来解决这个困境:
在我们的订单系统案例中,每个订单处理耗时约50-300ms不等。假设维护窗口只有30分钟,我们需要计算:当前积压任务能否在时限内完成?如果不能,是强行中断还是申请延长维护时间?这就是优雅关闭的艺术。
调用shutdown()时,线程池会进入SHUTDOWN状态,这个过程就像餐厅挂出"停止营业"的牌子:
java复制// 典型shutdown使用场景
executor.shutdown();
while (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
log.info("仍有{}个订单处理中...", executor.getActiveCount());
}
关键特性:
实测发现,对于CPU密集型任务,shutdown后线程利用率会逐渐下降,就像餐厅里顾客陆续离开的过程。但在IO密集型场景下,可能因网络延迟导致"长尾效应"。
当时间紧迫时,shutdownNow()会立即将线程池升级为STOP状态:
java复制List<Runnable> unfinishedTasks = executor.shutdownNow();
if (!unfinishedTasks.isEmpty()) {
log.warn("有{}个订单被强制取消", unfinishedTasks.size());
// 建议将未处理任务持久化
}
与shutdown的关键差异:
注意点:不是所有任务都能被中断。比如同步Socket IO可能不响应中断,这时需要配合线程的isInterrupted()状态做检查。
这个方法就像给关闭过程加上倒计时器:
java复制if (!executor.awaitTermination(5, TimeUnit.MINUTES)) {
log.error("系统关闭超时,可能存在资源泄漏风险");
// 触发告警或降级处理
}
重要特性:
在我们的订单系统中,建议设置超时时间=维护窗口时间-5分钟(安全余量)。
适合对数据一致性要求极高的场景:
java复制executor.shutdown();
try {
// 每30秒检查一次
while (!executor.awaitTermination(30, TimeUnit.SECONDS)) {
log.info("剩余任务量:{}", ((ThreadPoolExecutor)executor).getQueue().size());
// 动态调整策略
if (System.currentTimeMillis() > deadline) {
List<Runnable> dropped = executor.shutdownNow();
emergencySave(dropped);
break;
}
}
} catch (InterruptedException e) {
executor.shutdownNow();
Thread.currentThread().interrupt();
}
这种方案下我们曾处理过这样的案例:某次大促后,系统需要关闭时仍有8万订单待处理。通过动态监控,最终在45分钟内完成了所有订单,避免了数据丢失。
平衡型方案适合大多数业务场景:
java复制executor.shutdown();
if (!executor.awaitTermination(10, TimeUnit.MINUTES)) {
List<Runnable> unfinished = executor.shutdownNow();
if (!unfinished.isEmpty()) {
submitCompensationTask(unfinished);
}
}
关键参数建议:
适用于突发故障处理等紧急场景:
java复制List<Runnable> droppedTasks = executor.shutdownNow();
metrics.recordForceShutdown(); // 监控记录
// 异步处理未完成任务
recoveryExecutor.execute(() -> {
saveForRetry(droppedTasks);
});
这种方案下需要注意:
错误的配置可能导致优雅关闭失效:
java复制// 反例:可能导致shutdownNow失效
ExecutorService executor = Executors.newFixedThreadPool(4);
executor.execute(() -> {
while (true) { // 未检查中断状态
// 业务逻辑
}
});
建议配置方案:
任务实现方式直接影响关闭效果:
java复制// 良好实践示例
class OrderTask implements Runnable {
@Override
public void run() {
try {
while (!Thread.currentThread().isInterrupted()) {
// 可中断的业务逻辑
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // 恢复中断状态
saveProgress(); // 保存中间状态
}
}
}
建议在关闭流程中添加以下监控点:
我们曾通过监控发现一个典型问题:某支付系统关闭时总有约5%任务被强制终止。排查发现是因为这些任务涉及第三方支付网关调用,没有正确处理中断。修复后系统关闭时间从平均8分钟降至2分钟。