Timer 和 TimerTask 是 Java 标准库 (java.util 包) 中提供的一套简单、轻量级的定时任务解决方案,它非常适合在单个 JVM 进程中执行一些周期性的或延迟执行的任务。

核心概念
要理解 Timer,你需要了解两个主要的类:
a. java.util.Timer
这是定时器本身,它的主要职责是管理任务队列,并在指定的时间点执行任务。
- 作用:调度任务(
TimerTask)。 - 特点:
- 它是线程安全的,所有对
Timer的方法调用(如schedule,cancel)都是同步的。 - 它在后台运行一个名为 "Timer-Thread" 的守护线程(或非守护线程,取决于构造函数)来执行任务。
- 如果所有任务都被取消,并且没有任何活动的引用指向
Timer对象,那么它的后台线程会自动终止。
- 它是线程安全的,所有对
b. java.util.TimerTask
这是一个抽象类,代表一个可以被 Timer 调度的任务。
- 作用:定义要执行的具体逻辑。
- 如何使用:你需要创建
TimerTask的子类,并重写其run()方法。run()方法中的代码就是定时任务要执行的内容。 - 特点:
- 它实现了
Runnable接口,因此它的核心逻辑就是一个run()方法。 - 一旦任务被
Timer执行完成(无论是正常执行还是被取消),它就不能被再次调度。
- 它实现了
如何使用 Timer?
使用 Timer 的基本步骤如下:

- 创建任务:定义一个
TimerTask的子类,并实现run()方法。 - 创建定时器:实例化一个
Timer对象。 - 调度任务:调用
Timer的schedule()方法,将任务和执行时间/周期关联起来。
Timer 的调度方法详解
Timer 提供了多个 schedule 方法,以满足不同的调度需求,主要分为两类:一次性任务和周期性任务。
a. 一次性任务
只执行一次任务。
// schedule(TimerTask task, long delay) // 在 delay 毫秒后执行一次任务 timer.schedule(new MyTask(), 5000); // 5秒后执行
// schedule(TimerTask task, Date time) // 在指定的 time 时间点执行一次任务 Date futureDate = new Date(System.currentTimeMillis() + 10000); // 10秒后 timer.schedule(new MyTask(), futureDate);
b. 周期性任务
在第一次执行后,会以固定的间隔重复执行。
重要:周期是从任务开始执行的时间点开始计算的,而不是从任务结束的时间点。
// schedule(TimerTask task, long delay, long period) // 在 delay 毫秒后首次执行,之后每隔 period 毫秒重复执行一次 timer.schedule(new MyTask(), 1000, 2000); // 1秒后开始,之后每2秒执行一次
// schedule(TimerTask task, Date firstTime, long period) // 在 firstTime 时间点首次执行,之后每隔 period 毫秒重复执行一次 Date firstTime = new Date(System.currentTimeMillis() + 3000); timer.schedule(new MyTask(), firstTime, 3000); // 3秒后开始,之后每3秒执行一次
c. 固定延迟的周期性任务 (Fixed-Delay)
这是 schedule 方法的默认行为,它确保两次任务执行之间至少间隔 period 毫秒,如果某次任务执行时间超过了 period,那么下一次任务会等到这次任务完成后,再等待 period 毫秒才执行。
// 示例:period = 2000ms // T1: 任务开始执行,耗时 1500ms // T2: T1 + 1500ms = 1500ms,此时距离上次任务开始才1500ms,还需等待 500ms 才能开始下一次执行。 // T3: T2 + 2000ms = 3500ms
d. 固定频率的周期性任务 (Fixed-Rate)
Timer 还提供了 scheduleAtFixedRate 方法,它更注重任务执行的频率,它会尽量以固定的频率(period 毫秒)执行任务,如果某次任务执行被延迟了,后续的执行会“追赶”上来,以弥补延迟的时间,但可能会导致任务在短时间内连续执行多次。
// scheduleAtFixedRate(TimerTask task, long delay, long period) // 在 delay 毫秒后首次执行,之后以固定的频率 period 毫秒重复执行 timer.scheduleAtFixedRate(new MyTask(), 1000, 2000);
schedule vs scheduleAtFixedRate 的区别:
| 特性 | schedule() (固定延迟) |
scheduleAtFixedRate() (固定频率) |
|---|---|---|
| 核心思想 | 保证两次执行之间的间隔。 | 保证任务执行的频率。 |
| 执行时机 | 上次任务结束后,再等待 period 毫秒。 |
尝试在 period 毫秒的固定时间点上执行。 |
| 延迟处理 | 如果任务执行超时,后续执行会顺延。 | 如果任务执行超时,后续执行会“追赶”,可能导致任务重叠执行。 |
| 适用场景 | 任务执行时间不确定或波动较大,不希望任务堆积,数据处理、备份。 | 任务执行时间相对固定,需要严格保证频率,心跳检测、数据同步。 |
完整代码示例
下面是一个完整的示例,展示了如何创建、调度和取消一个定时任务。
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
public class TimerDemo {
// 1. 定义任务
static class MyTask extends TimerTask {
@Override
public void run() {
System.out.println("任务执行时间: " + new Date());
// 模拟一个耗时较长的任务
try {
Thread.sleep(1500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws InterruptedException {
// 2. 创建定时器
Timer timer = new Timer();
System.out.println("定时器启动,准备调度任务...");
// 3. 调度任务
// 首次延迟1秒,之后每2秒执行一次 (固定延迟)
// timer.schedule(new MyTask(), 1000, 2000);
// 首次延迟1秒,之后每2秒执行一次 (固定频率)
timer.scheduleAtFixedRate(new MyTask(), 1000, 2000);
// 让主线程运行一段时间,以便观察定时任务的执行
Thread.sleep(10000); // 10秒
// 4. 取消定时器
System.out.println("10秒后,取消定时器。");
timer.cancel(); // 取消所有任务,并终止定时器线程
// 再次尝试调度任务会抛出 IllegalStateException
// timer.schedule(new MyTask(), 1000); // 这行代码会抛出异常
}
}
执行结果分析 (使用 scheduleAtFixedRate):
你会看到类似下面的输出,注意看任务开始执行的时间戳,你会发现它们之间的间隔并不是严格的2秒,因为任务本身执行了1.5秒,系统会“追赶”时间。
定时器启动,准备调度任务...
任务执行时间: Thu Nov 30 10:30:01 CST 2025
任务执行时间: Thu Nov 30 10:30:03 CST 2025 // 距离上一个开始时间约2秒
任务执行时间: Thu Nov 30 10:30:05 CST 2025 // 距离上一个开始时间约2秒
任务执行时间: Thu Nov 30 10:30:07 CST 2025 // 距离上一个开始时间约2秒
10秒后,取消定时器。
Timer 的优缺点和适用场景
优点
- 简单易用:API 非常直观,对于简单的定时任务,几行代码就能搞定。
- JDK 内置:无需引入第三方库,开箱即用。
- 功能完备:支持一次性、周期性、固定延迟、固定频率等多种调度方式。
缺点
-
功能有限:不支持更复杂的调度场景,如 cron 表达式(如
0 0 12 * * ?每天12点执行)、任务依赖、任务分组等。 -
单线程模型:
Timer内部只有一个后台线程来执行所有任务,如果一个任务执行时间过长,会阻塞其他任务的执行,上面的例子中,MyTask执行了5秒,那么其他所有任务都会被推迟5秒。 -
异常处理脆弱:
TimerTask的run()方法抛出了一个未捕获的异常,会导致Timer的后台线程终止,并且该Timer对象中的所有剩余任务都不会再被执行,这是一个非常严重的问题。// 错误示范 timer.schedule(new TimerTask() { @Override public void run() { System.out.println("任务开始"); int i = 1 / 0; // 抛出 ArithmeticException System.out.println("任务结束"); // 这行代码不会执行 } }, 1000); // 这个任务永远不会被执行,因为第一个任务抛出了异常 timer.schedule(new TimerTask() { @Override public void run() { System.out.println("第二个任务"); } }, 2000); // 输出只会是 "任务开始",然后程序会报错,Timer线程终止。
适用场景
- 简单的、独立的、执行时间较短的后台任务。
- 个人项目、小型应用或原型开发。
- 不需要复杂调度策略的场景。
现代替代方案:ScheduledExecutorService
对于任何生产环境或稍微复杂的应用,强烈推荐使用 java.util.concurrent 包中的 ScheduledExecutorService,它是 Timer 的现代替代品,解决了 Timer 的所有主要缺点。
ScheduledExecutorService 的优势:
- 基于线程池:它使用一个线程池来执行任务,因此不会因为一个任务的失败而影响其他任务,也不会因为一个任务执行时间过长而阻塞所有任务。
- 更健壮:任务抛出未捕获异常时,只会影响该任务本身,线程池中的其他线程可以继续工作。
- 功能更强大:API 更灵活,支持
Future,可以更好地控制任务的执行和获取结果。 - 更现代:是 Java 并发包的一部分,设计更符合现代多线程编程范式。
ScheduledExecutorService 示例
import java.util.Date;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class ScheduledExecutorServiceDemo {
public static void main(String[] args) throws InterruptedException {
// 创建一个单线程的调度执行服务
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
System.out.println("调度器启动...");
// 创建任务
Runnable task = () -> {
System.out.println("任务执行时间: " + new Date());
try {
Thread.sleep(1500); // 模拟耗时任务
} catch (InterruptedException e) {
e.printStackTrace();
}
};
// 调度任务:1秒后开始,之后每2秒执行一次 (固定延迟)
// scheduler.scheduleWithFixedDelay(task, 1, 2, TimeUnit.SECONDS);
// 调度任务:1秒后开始,之后每2秒执行一次 (固定频率)
scheduler.scheduleAtFixedRate(task, 1, 2, TimeUnit.SECONDS);
// 运行一段时间后关闭
Thread.sleep(10000);
System.out.println("10秒后,关闭调度器。");
scheduler.shutdown(); // 优雅关闭,会等待正在执行的任务完成
}
}
| 特性 | java.util.Timer |
java.util.concurrent.ScheduledExecutorService |
|---|---|---|
| 线程模型 | 单线程 | 线程池 |
| 异常处理 | 一个任务失败会终止整个定时器 | 一个任务失败不影响其他任务 |
| 功能 | 简单,不支持复杂调度 | 强大,支持 Future 等高级特性 |
| 适用性 | 简单场景、学习 | 生产环境、任何复杂场景 |
| 推荐度 | ⭐⭐ (仅用于简单演示) | ⭐⭐⭐⭐⭐ (强烈推荐) |
- 学习目的:
Timer是了解定时任务基本概念的好工具。 - 实际开发:请优先使用
ScheduledExecutorService,它是更健壮、更强大、更现代的解决方案,只有在极其简单的、一次性的脚本中,为了代码简洁才可能考虑Timer。
