1. Timer – 基础概念

TimerTimerTask 是 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 的 TimerTimerTask 是一套简单但有效的定时任务调度机制,适合轻量级场景。但如果你需要更强大的调度功能,比如支持集群、持久化任务等,可以考虑使用 Quartz 等专业调度框架。

示例代码可在 GitHub 查看:Timer 和 TimerTask 示例


原始标题:Java - Timer