ScheduledExecutorService是Java中健壮、线程安全的定时调度工具,提供schedule、scheduleAtFixedRate、scheduleWithFixedDelay三类核心方法,推荐用Executors.newScheduledThreadPool创建并自定义线程工厂,注意异常处理、任务重叠及优雅关闭。

ScheduledExecutorService 是 Java 并发包中用于定时与周期性任务调度的核心工具,比传统 Timer 更健壮、线程安全、支持多线程执行,且能复用线程池资源。它不直接继承 ExecutorService,但实现了其接口,本质是增强版的线程池调度器。
核心方法怎么选?按场景对号入座
它提供四类调度方法,关键区别在“是否延迟”和“是否重复”:
-
schedule(Runnable/Callable, delay, TimeUnit):只执行一次,延迟指定时间后运行 -
scheduleAtFixedRate(Runnable, initialDelay, period, TimeUnit):固定频率——从首次执行开始,每隔 period 时间触发一次(即使前次未完成,也可能并发) -
scheduleWithFixedDelay(Runnable, initialDelay, delay, TimeUnit):固定延迟——前一次执行 结束后,再等 delay 时间才启动下一次(更稳妥,避免堆积) - 所有方法都返回
ScheduledFuture<?>,可用于取消(cancel(true))、查询状态或获取结果(对Callable)
创建方式:推荐使用 Executors.newScheduledThreadPool
不要用无界线程池或单线程硬扛高并发定时任务:
-
Executors.newScheduledThreadPool(3):创建含 3 个核心线程的池,适合中低频、需并行执行多个定时任务的场景 -
Executors.newSingleThreadScheduledExecutor():单线程串行执行,适合任务间有强顺序依赖,或需避免并发冲突(如共享文件写入) - 避免
new ScheduledThreadPoolExecutor(0)或传入Integer.MAX_VALUE线程数——可能引发 OOM 或线程争用 - 生产环境建议自定义线程工厂(设置名称、捕获未处理异常),例如:
new ThreadFactoryBuilder().setNameFormat("sched-task-%d").setUncaughtExceptionHandler(...).build()
常见陷阱与应对技巧
看似简单,实际容易踩坑:
立即学习“Java免费学习笔记(深入)”;
-
任务抛异常不终止调度:默认情况下,异常只会终止当次执行,后续仍按计划触发。务必在
Runnable内 try-catch,或用Callable+future.get()主动检查 -
fixedRate 下任务超时导致重叠:若某次执行耗时 > period,下一轮会在前一轮未结束时启动(可能造成数据竞争)。此时应改用
scheduleWithFixedDelay,或在任务内加锁/状态判断 -
shutdown 后任务仍可能执行:调用
shutdown()不会中断已提交但未开始的任务;要彻底停止,需先shutdown(),再awaitTermination(),必要时shutdownNow()尝试中断正在运行的任务 -
时间精度受系统时钟和线程调度影响:JDK 8+ 默认基于
System.nanoTime(),精度通常毫秒级,不适合微秒级实时控制
一个轻量实用示例:每5秒检查健康状态,失败则告警
体现初始化、调度、异常防护与优雅关闭:
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1,
new ThreadFactoryBuilder().setNameFormat("health-check-%d").build());
ScheduledFuture<?> future = scheduler.scheduleWithFixedDelay(() -> {
try {
if (!checkHealth()) {
alert("Service unhealthy!");
}
} catch (Exception e) {
log.error("Health check failed", e);
}
}, 0, 5, TimeUnit.SECONDS);
// 应用关闭时
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
future.cancel(true);
scheduler.shutdown();
try {
if (!scheduler.awaitTermination(10, TimeUnit.SECONDS)) {
scheduler.shutdownNow();
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}));

















