【JavaSE】【多线程】定时器

【JavaSE】【多线程】定时器全面详解
—— 从 Timer 到 ScheduledThreadPoolExecutor,再到现代最佳实践

在 Java 多线程开发中,定时任务 是非常常见的需求:每隔 5 秒刷新缓存、每天凌晨执行报表、延迟 10 秒发送短信、cron 表达式定时等。本文系统梳理 Java 中定时器的演进与使用方式。

1. Java 定时器演进路线

定时器推出时间是否推荐核心问题适用场景
java.util.Timer + TimerTaskJDK 1.3❌ 不推荐单线程、异常导致整个定时器死亡学习/遗留代码
ScheduledExecutorServiceJDK 1.5✅ 强烈推荐线程池,支持多任务绝大多数业务场景
ScheduledThreadPoolExecutorJDK 1.5✅ 最佳ScheduledExecutorService 实现类生产环境首选
Spring @ScheduledSpring✅ 推荐注解驱动,集成简单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:00
  • 0 0/5 * * * ? → 每 5 分钟
  • 0 15 10 ? * MON-FRI → 工作日 10:15

5. 高级特性与注意事项

  1. 任务异常处理
  • ScheduledThreadPoolExecutor 默认只打印堆栈,不停止调度。
  • 可自定义 RejectedExecutionHandlerUncaughtExceptionHandler
  1. 线程池监控
   ThreadPoolExecutor executor = (ThreadPoolExecutor) scheduler;
   executor.getActiveCount();   // 当前活跃线程数
  1. 分布式定时任务
  • 单机够用 → ScheduledExecutorService / @Scheduled
  • 分布式 → Quartz + 数据库、XXL-JOB、Elastic-Job、PowerJob 等
  1. 精度问题
  • 定时器精度受 OS 时间片影响(毫秒级),对纳秒级精度需求建议使用 Disruptor + 专用线程。

总结口诀

  • 学习用 Timer(别在生产用)
  • JavaSE 项目用 ScheduledThreadPoolExecutor
  • Spring Boot 项目用 @Scheduled + TaskScheduler
  • 分布式用专业调度框架

需要我继续补充以下任意内容,随时告诉我:

  • ScheduledThreadPoolExecutor 源码核心原理(DelayedWorkQueue)
  • 手写一个支持 cron 的简易定时器
  • Spring Boot + @Scheduled 完整多数据源示例
  • 与 Quartz 的详细对比
  • 定时任务监控(Actuator + Prometheus)

掌握定时器后,你的 Java 多线程能力又上了一个台阶!加油!🚀

文章已创建 5103

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

相关文章

开始在上面输入您的搜索词,然后按回车进行搜索。按ESC取消。

返回顶部