1. Timer – 基础概念
Timer
和 TimerTask
是 Java 标准库中用于在后台线程中调度任务的两个工具类。简单来说:
TimerTask
是我们要执行的任务;Timer
则是负责调度这些任务的调度器。
✅ 这两个类属于 java.util
包,适合轻量级、简单的定时任务场景。
2. 单次调度任务
2.1. 延迟执行任务
我们可以通过 Timer
来安排一个只执行一次的任务。比如下面这段代码:
@Test
public void givenUsingTimer_whenSchedulingTaskOnce_thenCorrect() {
TimerTask task = new TimerTask() {
public void run() {
System.out.println("Task performed on: " + new Date() + "\n" +
"Thread's name: " + Thread.currentThread().getName());
}
};
Timer timer = new Timer("Timer");
long delay = 1000L;
timer.schedule(task, delay);
}
这里我们使用了 schedule(task, delay)
方法,让任务在 1 秒后执行。
⚠️ 注意:如果是在 JUnit 测试中运行这段代码,需要加上 Thread.sleep(delay * 2)
等待任务执行完毕,否则主线程结束,任务可能还没来得及执行。
2.2. 指定时间点执行任务
如果我们想在某个具体的时间点执行任务,可以使用 schedule(TimerTask, Date)
方法。
举个例子,假设我们要把旧数据库的数据迁移到新数据库中,可以这样写:
public class DatabaseMigrationTask extends TimerTask {
private List<String> oldDatabase;
private List<String> newDatabase;
public DatabaseMigrationTask(List<String> oldDatabase, List<String> newDatabase) {
this.oldDatabase = oldDatabase;
this.newDatabase = newDatabase;
}
@Override
public void run() {
newDatabase.addAll(oldDatabase);
}
}
然后我们指定迁移任务在两秒后执行:
List<String> oldDatabase = Arrays.asList("Harrison Ford", "Carrie Fisher", "Mark Hamill");
List<String> newDatabase = new ArrayList<>();
LocalDateTime twoSecondsLater = LocalDateTime.now().plusSeconds(2);
Date twoSecondsLaterAsDate = Date.from(twoSecondsLater.atZone(ZoneId.systemDefault()).toInstant());
new Timer().schedule(new DatabaseMigrationTask(oldDatabase, newDatabase), twoSecondsLaterAsDate);
任务会在指定时间点执行,并验证结果:
while (LocalDateTime.now().isBefore(twoSecondsLater)) {
assertThat(newDatabase).isEmpty();
Thread.sleep(500);
}
assertThat(newDatabase).containsExactlyElementsOf(oldDatabase);
✅ 任务在指定时间前不会执行,时间一到就立即执行。
3. 重复调度任务
3.1. 固定延迟执行(Fixed Delay)
固定延迟是指:每次任务执行完后,再等一段时间才执行下一次任务。
比如我们要每两秒发送一封邮件,但邮件发送时间可能不同,就会出现延迟累积:
new Timer().schedule(new NewsletterTask(), 0, 1000);
Thread.sleep(20000);
输出可能如下:
Email sent at: 2023-02-04T13:59:42.107
The duration of sending the mail will took: 4
Email sent at: 2023-02-04T13:59:46.109
The duration of sending the mail will took: 4
Email sent at: 2023-02-04T13:59:50.113
The duration of sending the mail will took: 1
✅ 每次执行之间至少间隔一秒,但由于前一个任务耗时,后续任务会延迟。
3.2. 固定频率执行(Fixed Rate)
固定频率是指:不管上一个任务是否延迟,都按预定时间点执行下一个任务。
使用 scheduleAtFixedRate()
方法:
new Timer().scheduleAtFixedRate(new NewsletterTask(), 0, 1000);
Thread.sleep(20000);
输出如下:
Email sent at: 2023-02-04T13:59:22.082
The duration of sending the mail will took: 1
Email sent at: 2023-02-04T13:59:23.082
The duration of sending the mail will took: 6
Email sent at: 2023-02-04T13:59:24.082
...
✅ 每个任务都在固定时间点执行,不受前一个任务的影响。
3.3. 每天执行一次任务
下面是一个每天执行一次任务的例子:
@Test
public void givenUsingTimer_whenSchedulingDailyTask_thenCorrect() {
TimerTask repeatedTask = new TimerTask() {
public void run() {
System.out.println("Task performed on " + new Date());
}
};
Timer timer = new Timer("Timer");
long delay = 1000L;
long period = 1000L * 60L * 60L * 24L;
timer.scheduleAtFixedRate(repeatedTask, delay, period);
}
✅ 任务每天执行一次,适用于日志清理、数据统计等场景。
4. 取消 Timer 和 TimerTask
4.1. 在任务内部取消
可以在 run()
方法中调用 cancel()
来取消当前任务:
@Test
public void givenUsingTimer_whenCancelingTimerTask_thenCorrect()
throws InterruptedException {
TimerTask task = new TimerTask() {
public void run() {
System.out.println("Task performed on " + new Date());
cancel();
}
};
Timer timer = new Timer("Timer");
timer.scheduleAtFixedRate(task, 1000L, 1000L);
Thread.sleep(1000L * 2);
}
✅ 任务只执行一次,之后自动取消。
4.2. 取消整个 Timer
也可以通过调用 timer.cancel()
来取消整个 Timer:
@Test
public void givenUsingTimer_whenCancelingTimer_thenCorrect()
throws InterruptedException {
TimerTask task = new TimerTask() {
public void run() {
System.out.println("Task performed on " + new Date());
}
};
Timer timer = new Timer("Timer");
timer.scheduleAtFixedRate(task, 1000L, 1000L);
Thread.sleep(1000L * 2);
timer.cancel();
}
✅ 所有任务都会被取消。
4.3. 停止线程
还可以通过中断线程的方式取消任务:
@Test
public void givenUsingTimer_whenStoppingThread_thenTimerTaskIsCancelled()
throws InterruptedException {
TimerTask task = new TimerTask() {
public void run() {
System.out.println("Task performed on " + new Date());
// TODO: stop the thread here
}
};
Timer timer = new Timer("Timer");
timer.scheduleAtFixedRate(task, 1000L, 1000L);
Thread.sleep(1000L * 2);
}
⚠️ 一般不推荐直接调用 Thread.stop()
,因为它是不安全的。
5. Timer vs ExecutorService
Java 中还可以使用 ScheduledExecutorService
来实现类似的定时任务功能:
@Test
public void givenUsingExecutorService_whenSchedulingRepeatedTask_thenCorrect()
throws InterruptedException {
TimerTask repeatedTask = new TimerTask() {
public void run() {
System.out.println("Task performed on " + new Date());
}
};
ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
long delay = 1000L;
long period = 1000L;
executor.scheduleAtFixedRate(repeatedTask, delay, period, TimeUnit.MILLISECONDS);
Thread.sleep(delay + period * 3);
executor.shutdown();
}
两者主要区别:
特性 | Timer | ScheduledExecutorService |
---|---|---|
系统时间敏感 | ✅ 敏感 | ❌ 不敏感 |
并发能力 | ❌ 单线程 | ✅ 可配置多线程 |
异常处理 | ❌ 抛异常后整个 Timer 停止 | ✅ 只取消当前任务 |
✅ 推荐使用 ScheduledExecutorService
,它更健壮、灵活。
6. 总结
Java 的 Timer
和 TimerTask
是一套简单但有效的定时任务调度机制,适合轻量级场景。但如果你需要更强大的调度功能,比如支持集群、持久化任务等,可以考虑使用 Quartz 等专业调度框架。
示例代码可在 GitHub 查看:Timer 和 TimerTask 示例