【JavaSE】【多线程】定时器全面详解
—— 从 Timer 到 ScheduledThreadPoolExecutor,再到现代最佳实践
在 Java 多线程开发中,定时任务 是非常常见的需求:每隔 5 秒刷新缓存、每天凌晨执行报表、延迟 10 秒发送短信、cron 表达式定时等。本文系统梳理 Java 中定时器的演进与使用方式。
1. Java 定时器演进路线
| 定时器 | 推出时间 | 是否推荐 | 核心问题 | 适用场景 |
|---|---|---|---|---|
java.util.Timer + TimerTask | JDK 1.3 | ❌ 不推荐 | 单线程、异常导致整个定时器死亡 | 学习/遗留代码 |
ScheduledExecutorService | JDK 1.5 | ✅ 强烈推荐 | 线程池,支持多任务 | 绝大多数业务场景 |
ScheduledThreadPoolExecutor | JDK 1.5 | ✅ 最佳 | ScheduledExecutorService 实现类 | 生产环境首选 |
Spring @Scheduled | Spring | ✅ 推荐 | 注解驱动,集成简单 | Spring Boot 项目 |
| Quartz / XXL-JOB / Elastic-Job | 第三方 | 视情况 | 分布式、持久化、可视化 | 分布式、复杂调度 |
结论:JavaSE 项目首选 ScheduledThreadPoolExecutor,Spring 项目优先 @Scheduled。
2. 经典但已过时的 Timer(了解即可)
Timer timer = new Timer("MyTimer", true); // true 表示守护线程
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("定时任务执行:" + new Date());
}
}, 1000, 2000); // 延迟1秒后,每2秒执行一次
致命缺陷:
- 单线程:所有任务串行执行,一个任务卡住会导致后续全部延误。
- 异常不捕获:任务抛异常会导致整个 Timer 线程死亡,后续任务全部停止。
- 不灵活:不支持固定频率(fixedRate)、不支持线程池。
结论:生产环境严禁使用 Timer。
3. 现代推荐方案:ScheduledExecutorService
3.1 核心接口与常用方法
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(10);
scheduler.schedule(() -> System.out.println("延迟执行"), 5, TimeUnit.SECONDS);
scheduler.scheduleAtFixedRate(() -> System.out.println("固定速率"),
2, 3, TimeUnit.SECONDS); // 初始延迟2s,之后每3s执行一次
scheduler.scheduleWithFixedDelay(() -> System.out.println("固定延迟"),
2, 3, TimeUnit.SECONDS); // 上次执行完后延迟3s再执行
关键区别:
scheduleAtFixedRate:固定速率(以开始时间为基准),适合需要严格周期的任务。scheduleWithFixedDelay:固定延迟(以上次结束时间为基准),适合任务执行时间不固定的场景。
3.2 完整生产示例(推荐写法)
public class ScheduledTaskDemo {
private static final ScheduledExecutorService scheduler =
Executors.newScheduledThreadPool(5, r -> {
Thread t = new Thread(r);
t.setDaemon(true); // 守护线程,JVM退出时自动结束
t.setName("scheduled-task-");
return t;
});
public static void main(String[] args) {
// 1. 延迟一次执行
scheduler.schedule(() -> System.out.println("5秒后执行"), 5, TimeUnit.SECONDS);
// 2. 固定速率执行(推荐大多数场景)
scheduler.scheduleAtFixedRate(new RunnableTask(),
0, 10, TimeUnit.SECONDS);
// 3. 固定延迟执行
scheduler.scheduleWithFixedDelay(new RunnableTask(),
0, 10, TimeUnit.SECONDS);
}
static class RunnableTask implements Runnable {
@Override
public void run() {
try {
System.out.println("任务执行开始:" + new Date());
// 模拟业务耗时
Thread.sleep(1500);
System.out.println("任务执行结束:" + new Date());
} catch (Exception e) {
e.printStackTrace();
}
}
}
// JVM 关闭时优雅关闭线程池
static {
Runtime.getRuntime().addShutdownHook(new Thread(scheduler::shutdown));
}
}
重要建议:
- 使用自定义 ThreadFactory 设置线程名称,便于日志和监控。
- 设置为守护线程,避免阻止 JVM 正常退出。
- 捕获异常,防止单个任务失败影响其他任务。
- 业务量大时合理设置核心线程数(建议 5~50,根据 CPU 核数和任务特性调整)。
- 程序结束时调用
shutdown()或shutdownNow()。
4. Spring Boot 项目中最推荐的写法(@Scheduled)
@Configuration
@EnableScheduling
public class SchedulingConfig {
@Bean
public TaskScheduler taskScheduler() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(10);
scheduler.setThreadNamePrefix("spring-scheduled-");
scheduler.setDaemon(true);
scheduler.initialize();
return scheduler;
}
}
// 使用注解
@Service
public class ReportService {
@Scheduled(cron = "0 0 2 * * ?") // 每天凌晨 2 点
public void generateDailyReport() { ... }
@Scheduled(fixedRate = 5000) // 每5秒执行一次(固定速率)
public void refreshCache() { ... }
@Scheduled(fixedDelay = 10000) // 上次执行完后延迟10秒
public void cleanTempFiles() { ... }
}
cron 表达式常用示例:
0 0 2 * * ?→ 每天 2:000 0/5 * * * ?→ 每 5 分钟0 15 10 ? * MON-FRI→ 工作日 10:15
5. 高级特性与注意事项
- 任务异常处理:
ScheduledThreadPoolExecutor默认只打印堆栈,不停止调度。- 可自定义
RejectedExecutionHandler和UncaughtExceptionHandler。
- 线程池监控:
ThreadPoolExecutor executor = (ThreadPoolExecutor) scheduler;
executor.getActiveCount(); // 当前活跃线程数
- 分布式定时任务:
- 单机够用 → ScheduledExecutorService / @Scheduled
- 分布式 → Quartz + 数据库、XXL-JOB、Elastic-Job、PowerJob 等
- 精度问题:
- 定时器精度受 OS 时间片影响(毫秒级),对纳秒级精度需求建议使用 Disruptor + 专用线程。
总结口诀:
- 学习用 Timer(别在生产用)
- JavaSE 项目用
ScheduledThreadPoolExecutor - Spring Boot 项目用
@Scheduled+TaskScheduler - 分布式用专业调度框架
需要我继续补充以下任意内容,随时告诉我:
ScheduledThreadPoolExecutor源码核心原理(DelayedWorkQueue)- 手写一个支持 cron 的简易定时器
- Spring Boot + @Scheduled 完整多数据源示例
- 与 Quartz 的详细对比
- 定时任务监控(Actuator + Prometheus)
掌握定时器后,你的 Java 多线程能力又上了一个台阶!加油!🚀