scheduledexecutorservice 比 timer 更可靠,因其采用多线程机制,单任务异常不影响整体调度;timer 依赖单线程,未捕获异常会导致线程终止、全部任务停摆。

ScheduledExecutorService 比 Timer 更可靠,尤其在任务抛异常、执行时间长、多线程调度时——它不会像 Timer 那样“挂掉整个调度器”。
为什么 Timer 一出错就停摆?
Timer 内部只用单个后台线程跑所有任务。一旦某个 TimerTask 抛了未捕获异常,这个线程就终止,后续所有定时任务全停,且无日志、无提示、不重启。
-
Timer的异常传播路径是:任务 →TimerThread→ 线程死亡 → 调度器瘫痪 -
ScheduledExecutorService(比如Executors.newScheduledThreadPool(2))每个任务在独立线程里跑,一个崩不影响其他 - 哪怕你只用 1 个核心线程(
newSingleThreadScheduledExecutor()),异常也不会让调度器“失联”,只会跳过当前任务,继续下一次调度
怎么把 Timer 改成 ScheduledExecutorService?
不是简单替换类名,关键在任务提交方式和生命周期管理。
- 取消
Timer.schedule(new TimerTask(){...}, delay),改用scheduler.schedule(() -> {...}, delay, TimeUnit.SECONDS) - 重复任务别用
scheduleAtFixedRate直接套用旧逻辑——它默认“严格按间隔触发”,若前次执行超时,下次会立即补上(可能堆积)。更安全的是scheduleWithFixedDelay,等前次结束再等 delay - 必须显式调用
scheduler.shutdown()或shutdownNow(),否则 JVM 无法退出(Timer同样有这问题,但常被忽略) - 如果原
Timer是 static 单例,换成ScheduledExecutorService也要注意线程池复用与关闭时机,避免内存泄漏
scheduleWithFixedDelay 和 scheduleAtFixedRate 到底差在哪?
看的是“间隔从哪算起”。这个差异在任务执行不稳定时特别致命。
立即学习“Java免费学习笔记(深入)”;
-
scheduleAtFixedRate:从第一次 计划开始时间 起,每 N 秒触发一次。比如设 5 秒一跑,第 1 次 0s 开始、第 2 次 5s、第 3 次 10s……就算第 2 次卡到 8s 才结束,第 3 次仍会在 10s 强制启动(可能并发) -
scheduleWithFixedDelay:从上一次 实际结束时间 起,延迟 N 秒再启动下一次。第 1 次 0s 开始、耗时 4s;第 2 次就在 4+3=7s 启动;哪怕某次耗时 10s,下一次也是 10+3=13s 启动——天然防堆积 - 日常轮询、健康检查、清理缓存等场景,优先选
scheduleWithFixedDelay
常见报错:RejectedExecutionException 怎么回事?
不是代码写错了,是线程池被关了还在往里塞任务。
- 典型错误现象:
java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.ScheduledFutureTask@... rejected from java.util.concurrent.ScheduledThreadPoolExecutor@...[Terminated, pool size = 0, ...] - 原因:调用了
shutdown()或shutdownNow()后,又执行schedule(...) - 解决办法:提交前加判断
if (!scheduler.isShutdown()) { scheduler.schedule(...); },或用try-catch包住提交逻辑 - 更稳妥的做法是把调度器封装成服务类,提供
start()/stop()方法统一管控状态,避免裸露ScheduledExecutorService实例
真正麻烦的从来不是换 API,而是旧 Timer 可能散落在十几个地方、有的没关、有的共享、有的依赖静态初始化顺序——上线前务必 grep 全局,逐个确认生命周期归属。










