timer是单线程调度器,timertask是任务载体,需配对使用;schedule()为固定延迟,scheduleatfixedrate()为固定速率;必须调用timer.cancel()释放资源;推荐用scheduledexecutorservice替代。

Timer 和 TimerTask 的基本协作方式
Java 中 Timer 是调度器,TimerTask 是具体要执行的任务;二者必须配对使用,不能单独运行任务。一个 Timer 可以调度多个 TimerTask,但所有任务默认在同一个后台线程中串行执行——这意味着如果某个任务耗时过长,会阻塞后续任务的触发时间。
常见错误现象:TimerTask 的 run() 方法里抛出未捕获异常,会导致整个 Timer 线程终止,后续所有任务都不再执行(不会自动重启)。
- 继承
TimerTask并重写run(),不要直接 new Runnable - 任务逻辑尽量轻量,避免 I/O 或长时间计算;如需异步处理,请在
run()内显式启新线程或提交到线程池 - 务必在
run()中用 try-catch 包裹全部逻辑,防止意外中断调度器
schedule() 与 scheduleAtFixedRate() 的关键区别
schedule() 是“固定延迟”,下次执行时间 = 上次 实际结束时间 + 延迟;scheduleAtFixedRate() 是“固定速率”,下次执行时间 = 上次 计划开始时间 + 间隔 —— 即使某次执行超时,也会尝试“追赶”。
使用场景举例:做心跳上报适合 scheduleAtFixedRate();做缓存清理、日志轮转更适合 schedule(),避免因前次卡顿导致密集连发。
-
schedule(task, delay):只执行一次,delay 毫秒后运行 -
schedule(task, firstTime, period):首次在firstTime运行,之后每隔period毫秒执行(延迟模式) -
scheduleAtFixedRate(task, firstTime, period):首次在firstTime运行,之后严格按周期推进(速率模式)
如何安全取消定时任务和释放资源
Timer 不会自动关闭,即使所有任务都已 cancel,它仍持有线程,可能造成内存泄漏或 JVM 无法退出。必须显式调用 timer.cancel(),且该操作会终止线程并清空任务队列。
容易踩的坑:TimerTask.cancel() 只是把当前任务从队列中移除,不影响其他任务,也不停止 Timer 线程;若任务已开始执行,cancel() 对正在运行的 run() 无任何影响。
- 在应用 shutdown 或组件销毁时,优先调用
timer.cancel() - 如果任务中开启了子线程或打开了文件/连接,应在
TimerTask的run()内自行管理生命周期,cancel()不会帮你关 - 不要反复 new Timer;建议单例复用,或配合依赖注入容器管理其生命周期
为什么现在更推荐 ScheduledExecutorService
Timer 是单线程模型,容错差、功能弱;而 ScheduledExecutorService(如 Executors.newScheduledThreadPool(1))支持多线程、可配置拒绝策略、能返回 ScheduledFuture 用于取消或获取结果,且异常不会导致整个调度器崩溃。
兼容性影响:JDK 5+ 全面支持,Timer 虽仍可用,但在新项目或微服务中几乎不被选用。迁移成本低——只需把 TimerTask 改为 Runnable,schedule() 替换为 schedule() 或 scheduleAtFixedRate() 方法即可。
真正麻烦的是遗留代码里那些没加 try-catch 的 TimerTask,上线后某天突然静默停摆,排查起来比改掉还费劲。










