杰瑞科技汇

Java姆Java定时任务Timer如何精准执行?

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

Java姆Java定时任务Timer如何精准执行?-图1
(图片来源网络,侵删)

核心概念

要理解 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 的基本步骤如下:

Java姆Java定时任务Timer如何精准执行?-图2
(图片来源网络,侵删)
  1. 创建任务:定义一个 TimerTask 的子类,并实现 run() 方法。
  2. 创建定时器:实例化一个 Timer 对象。
  3. 调度任务:调用 Timerschedule() 方法,将任务和执行时间/周期关联起来。

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 的优缺点和适用场景

优点

  1. 简单易用:API 非常直观,对于简单的定时任务,几行代码就能搞定。
  2. JDK 内置:无需引入第三方库,开箱即用。
  3. 功能完备:支持一次性、周期性、固定延迟、固定频率等多种调度方式。

缺点

  1. 功能有限:不支持更复杂的调度场景,如 cron 表达式(如 0 0 12 * * ? 每天12点执行)、任务依赖、任务分组等。

  2. 单线程模型Timer 内部只有一个后台线程来执行所有任务,如果一个任务执行时间过长,会阻塞其他任务的执行,上面的例子中,MyTask 执行了5秒,那么其他所有任务都会被推迟5秒。

  3. 异常处理脆弱TimerTaskrun() 方法抛出了一个未捕获的异常,会导致 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 的优势:

  1. 基于线程池:它使用一个线程池来执行任务,因此不会因为一个任务的失败而影响其他任务,也不会因为一个任务执行时间过长而阻塞所有任务。
  2. 更健壮:任务抛出未捕获异常时,只会影响该任务本身,线程池中的其他线程可以继续工作。
  3. 功能更强大:API 更灵活,支持 Future,可以更好地控制任务的执行和获取结果。
  4. 更现代:是 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
分享:
扫描分享到社交APP
上一篇
下一篇