Timer 不推荐使用,因其单线程设计导致未捕获异常时整个调度器崩溃;schedule 基于上一次执行完成时间顺延,scheduleAtFixedRate 基于初始预定时间严格推进;应改用 ScheduledExecutorService。

Java 里 Timer 和 TimerTask 能实现基础定时任务,但不推荐在新项目中使用——它线程不安全、无法处理异常导致后续任务全部停摆、也不支持并发执行。
为什么 Timer 容易“一崩全停”
一个 Timer 实例背后只有一条后台线程(TimerThread),所有 TimerTask 都由它串行执行。一旦某个 TimerTask 的 run() 方法抛出未捕获异常,该线程会立即终止,之后注册的所有任务永远不会再触发。
- 常见错误现象:
Timer突然“静默失效”,控制台无报错,但任务不再执行 - 典型场景:网络请求失败没 try-catch、JSON 解析异常、数据库连接中断
- 无法恢复:线程挂了就挂了,没有重试或重启机制
schedule() 和 scheduleAtFixedRate() 的关键区别
两者都用于周期性调度,但对“延迟”和“执行节奏”的处理逻辑完全不同:
-
schedule(task, delay, period):以“上一次实际执行完成时间”为基准,延迟period后再启动下一次;若某次执行超时,下一次会自动顺延,不会堆积 -
scheduleAtFixedRate(task, firstTime, period):以“预定起始时间”为基准,严格按period间隔推进;若某次执行耗时过长,后续任务可能被压缩甚至并发触发(但Timer是单线程,所以实际是立即排队) - 性能影响:后者更容易因任务延迟引发队列积压,最终
OutOfMemoryError: Java heap space(大量未执行的TimerTask对象滞留在队列中)
如何避免 TimerTask 泄漏和资源未释放
TimerTask 是抽象类,子类常持有外部对象引用(如 Service、DAO、Context),若忘记取消或 Timer 未被回收,会导致 GC 无法清理,引发内存泄漏。
立即学习“Java免费学习笔记(深入)”;
- 必须显式调用
timer.cancel()—— 这会清空任务队列并终止线程,但不能重复调用 -
timerTask.cancel()只是从队列中移除该任务,不影响其他任务,也不终止线程 - 常见疏漏:Spring Bean 中用
@PostConstruct启动Timer,却没在@PreDestroy中调用cancel() - 建议封装:把
Timer和关键TimerTask绑定在一个管理类里,提供统一启停接口
替代方案比你想象中更简单
除非维护老系统,否则直接用 ScheduledExecutorService:
- 线程池可配置大小,天然支持并发执行多个任务
- 异常不会中断整个调度器,只会终止当前任务实例
- API 更清晰:
scheduleAtFixedRate()/scheduleWithFixedDelay()命名即语义 - 示例:
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2); scheduler.scheduleAtFixedRate(() -> { // 你的逻辑 }, 0, 5, TimeUnit.SECONDS);
真正复杂的是任务持久化、故障恢复、分布式协调——这些 Timer 和 ScheduledExecutorService 都不解决,得靠 Quartz 或 XXL-JOB 这类调度中间件。别在基础定时器上硬扛业务可靠性需求。










