java.util.Timer和java.util.TimerTask(JDK 内置,简单易用)ScheduledExecutorService(JDK 内置,功能更强大,推荐)- 第三方框架
Spring/Spring Boot(企业级应用首选)
java.util.Timer 和 java.util.TimerTask
这是 Java 早期提供的定时任务方案,非常简单,适合执行一些独立的、不需要复杂管理的后台任务。

核心概念
Timer: 一个调度器,负责管理TimerTask的执行,它是一个后台线程。TimerTask: 一个抽象类,继承它并重写run()方法,就可以定义你想要执行的任务。
基本用法
步骤:
- 创建一个
TimerTask的子类,并实现run()方法。 - 创建一个
Timer实例。 - 调用
Timer的schedule()或scheduleAtFixedRate()方法来安排任务。
示例代码:
import java.util.Timer;
import java.util.TimerTask;
public class TimerDemo {
public static void main(String[] args) {
// 1. 创建一个TimerTask任务
TimerTask task = new TimerTask() {
@Override
public void run() {
System.out.println("定时任务执行了!当前时间: " + new java.util.Date());
}
};
// 2. 创建一个Timer调度器
Timer timer = new Timer();
// 3. 安排任务执行
// 方式一:延迟1秒后执行,之后每隔2秒执行一次
// timer.schedule(task, 1000, 2000);
// 方式二:指定在未来的某个具体时间点执行,之后每隔2秒执行一次
// timer.scheduleAtFixedRate(task, new java.util.Date(System.currentTimeMillis() + 1000), 2000);
// 方式三:只延迟1秒后执行一次
timer.schedule(task, 1000);
// 为了让程序能持续运行,观察定时任务的效果
try {
Thread.sleep(10000); // 主线程睡眠10秒
} catch (InterruptedException e) {
e.printStackTrace();
}
// 4. 取消定时器(非常重要!)
timer.cancel();
System.out.println("定时器已取消。");
}
}
schedule vs scheduleAtFixedRate
-
schedule(TimerTask task, long delay, long period)- 特点:固定延迟执行。
- 逻辑:任务执行完成后,会等待
period毫秒,然后再执行下一次任务。 - 适用场景:不要求任务执行时间间隔绝对精确,允许因任务执行时间过长而导致间隔变长。
-
scheduleAtFixedRate(TimerTask task, long delay, long period)
(图片来源网络,侵删)- 特点:固定频率执行。
- 逻辑:它会尽量保证任务以
period毫秒为周期来执行,如果某个任务执行时间超过了period,它会“追赶”进度,尽快执行下一次任务,导致两次任务之间的间隔会小于period。 - 适用场景:要求任务执行频率尽可能稳定,例如每秒心跳一次。
Timer 的缺点
- 基于单线程:所有任务都在同一个线程中串行执行,如果一个任务执行时间过长,会阻塞其他任务的执行。
- 异常处理脆弱:
TimerTask中的run()方法抛出了未捕获的异常,会导致整个Timer线程终止,之后所有定时任务都不会再执行。 - 功能有限:无法灵活地管理任务(如暂停、恢复),也无法处理更复杂的调度逻辑(如“每天凌晨2点执行”)。
ScheduledExecutorService
从 Java 5 开始,java.util.concurrent 包提供了 ScheduledExecutorService,它是 ExecutorService 的扩展,功能更强大,是目前 Java 原生定时任务的首选方案。
核心概念
ScheduledExecutorService: 一个线程池,专门用于调度和执行延迟任务或周期性任务。ScheduledFuture: 代表一个已经调度的任务,可以用来获取任务的结果、取消任务或检查任务是否完成。
基本用法
步骤:
- 通过
Executors工厂类创建一个ScheduledExecutorService实例。 - 使用
schedule(),scheduleAtFixedRate(),scheduleWithFixedDelay()方法提交任务。 - 当不再需要时,调用
shutdown()关闭线程池。
示例代码:
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class ScheduledExecutorServiceDemo {
public static void main(String[] args) {
// 1. 创建一个线程池,核心线程数为1
ScheduledExecutorService scheduledExecutor = Executors.newScheduledThreadPool(1);
// 2. 定义一个任务(使用Runnable或Callable)
Runnable task = () -> {
System.out.println("线程池定时任务执行了!当前时间: " + new java.util.Date());
// 模拟一个耗时较长的任务
try {
Thread.sleep(1500);
} catch (InterruptedException e) {
e.printStackTrace();
}
};
// 3. 安排任务执行
// 方式一:延迟1秒后执行,之后每隔2秒执行一次(固定延迟)
// scheduledExecutor.scheduleWithFixedDelay(task, 1, 2, TimeUnit.SECONDS);
// 方式二:延迟1秒后执行,之后每隔2秒执行一次(固定频率)
// 如果任务执行时间超过2秒,下一次任务会“追赶”
scheduledExecutor.scheduleAtFixedRate(task, 1, 2, TimeUnit.SECONDS);
// 方式三:只延迟1秒后执行一次
// scheduledExecutor.schedule(task, 1, TimeUnit.SECONDS);
// 为了观察效果
try {
Thread.sleep(20000); // 主线程睡眠20秒
} catch (InterruptedException e) {
e.printStackTrace();
}
// 4. 关闭线程池(非常重要!)
scheduledExecutor.shutdown();
System.out.println("线程池已关闭。");
}
}
ScheduledExecutorService 的优点
- 基于线程池:可以管理多个线程,避免了
Timer的单线程阻塞问题,一个任务失败不会影响其他任务。 - 异常处理健壮:任务抛出的异常不会导致整个线程池终止,只会影响当前任务。
- 功能更强大:提供了更丰富的 API,可以方便地取消任务(通过
ScheduledFuture),并且支持Callable任务,可以获取返回值。 - 更灵活的关闭机制:提供了
shutdown()和shutdownNow()方法,可以优雅地关闭线程池。
Spring / Spring Boot 的定时任务 (@Scheduled)
在企业级应用开发中,我们通常使用 Spring 框架来管理定时任务,Spring 提供了基于注解的 @Scheduled,使用起来非常简洁和方便。

核心概念
@EnableScheduling: 在 Spring 配置类(或带有@Configuration的类)上添加此注解,以启用对@Scheduled注解的支持。@Scheduled: 在方法上添加此注解,该方法将成为一个定时任务。
基本用法
步骤:
- 在 Spring Boot 的主启动类或配置类上添加
@EnableScheduling。 - 在需要执行定时任务的方法上添加
@Scheduled注解,并配置其执行规则。
示例代码:
主启动类 / 配置类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication
@EnableScheduling // 启用定时任务
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
定时任务服务类
import org.springframework.stereotype.Component;
import java.util.Date;
@Component // 必须被 Spring 容器管理
public class ScheduledTasks {
// cron 表达式,每5秒执行一次
@Scheduled(cron = "0/5 * * * * ?")
public void taskByCron() {
System.out.println("Cron定时任务执行了!当前时间: " + new Date());
}
// 固定延迟执行:上一次任务结束后,等待3秒再执行下一次
@Scheduled(fixedDelay = 3000)
public void taskWithFixedDelay() {
System.out.println("FixedDelay定时任务执行了!当前时间: " + new Date());
try {
Thread.sleep(1000); // 模拟耗时1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 固定频率执行:每隔3秒执行一次(不受任务执行时间影响)
@Scheduled(fixedRate = 3000)
public void taskWithFixedRate() {
System.out.println("FixedRate定时任务执行了!当前时间: " + new Date());
}
}
@Scheduled 的主要属性
cron: 最强大的属性,使用 Cron 表达式来定义复杂的调度规则。- 格式:
秒 分 时 日 月 周 - 示例:
"0 0 2 * * ?": 每天凌晨2点执行。"0 0/5 14,18 * * ?": 每天14点和18点,每隔5分钟执行一次。"0 15 10 ? * MON-FRI": 每周一至周五的10:15执行。
- 格式:
fixedDelay: 固定延迟,单位是毫秒,表示上一次任务执行完成后,延迟多长时间再执行下一次。fixedRate: 固定频率,单位是毫秒,表示每隔多长时间执行一次任务,与任务执行时间无关。initialDelay: 初始延迟,单位是毫秒,表示在第一次任务执行前,等待多长时间,通常与fixedDelay或fixedRate一起使用。@Scheduled(fixedRate = 3000, initialDelay = 1000)表示程序启动后1秒执行第一次,之后每隔3秒执行一次。
总结与对比
| 特性 | java.util.Timer |
ScheduledExecutorService |
Spring @Scheduled |
|---|---|---|---|
| 来源 | JDK 1.3 | JDK 1.5 | Spring Framework |
| 线程模型 | 单线程 | 线程池 | Spring Bean (默认单例,方法在Spring管理的线程中执行) |
| 异常处理 | 差,会终止整个调度器 | 好,异常只影响当前任务 | 好,异常由 Spring 框架统一处理 |
| 功能 | 基础,简单 | 强大,灵活 | 非常强大,支持Cron表达式,与Spring生态集成 |
| 适用场景 | 简单的、独立的任务 | Java原生应用中需要灵活调度的任务 | Spring / Spring Boot 应用 |
| 管理 | 手动 cancel() |
手动 shutdown() |
由 Spring 容器生命周期管理 |
| 推荐度 | ⭐⭐ (已过时,不推荐新项目使用) | ⭐⭐⭐⭐ (Java原生方案首选) | ⭐⭐⭐⭐⭐ (企业级应用首选) |
如何选择?
- 如果你在写一个简单的、独立的 Java 程序,没有引入任何框架,并且任务非常简单,可以考虑使用
ScheduledExecutorService。 - 如果你正在使用 Spring 或 Spring Boot 框架开发,请毫不犹豫地选择
@Scheduled,它与 Spring 的 IoC、AOP 等特性无缝集成,使用体验最好,功能也最完善。 - 尽量避免在新项目中使用
java.util.Timer,除非你维护的是一个古老的遗留系统,它的单线程和脆弱的异常处理机制是它致命的缺点。
