首页 区块链

深入剖析 Java 定时器 Timer:源码级原理与避坑指南

分类:区块链
字数: (9044)
阅读: (0145)
内容摘要:深入剖析 Java 定时器 Timer:源码级原理与避坑指南,

在日常 Java 开发中,我们经常需要用到定时任务,java.util.Timer 类就是一个常用的选择。但是,你是否真正了解 Timer 的底层实现原理?是否遇到过 Timer 带来的线程安全问题?或者任务执行时间过长导致后续任务延迟执行的问题?本文将深入 Timer 的源码,带你彻底搞懂它,并分享一些实战中的避坑经验。

Java学习之 Timer 源码详解

Timer 类是 Java 提供的一个简单的定时任务调度器,它允许你安排任务在未来的某个时间执行一次,或者定期重复执行。 要理解 Timer,我们需要关注它的几个核心组成部分:Timer 类本身、TimerTask 抽象类和底层的线程模型。

深入剖析 Java 定时器 Timer:源码级原理与避坑指南

Timer 的线程模型

Timer 的核心是一个单线程模型。当你创建一个 Timer 对象时,它会创建一个后台线程(TimerThread)来执行所有安排的任务。这意味着所有的 TimerTask 都是在这个线程中顺序执行的。 如果某个任务执行时间过长,会阻塞后续任务的执行。这与基于线程池的 ScheduledExecutorService 形成了鲜明对比,后者可以使用多个线程并行执行任务。

深入剖析 Java 定时器 Timer:源码级原理与避坑指南
public class Timer {
    // 关联的 TimerThread,负责执行任务
    private final TimerThread thread;
    // 任务队列,存放待执行的任务
    private final TaskQueue queue = new TaskQueue();
    // 是否取消 Timer
    private boolean cancelled = false;

    public Timer() {
        this("Timer-" + serialNumber()); // 给线程命名
    }

    Timer(String name) {
        thread = new TimerThread(queue); // 创建 TimerThread
        thread.setName(name); // 设置线程名称,方便排查问题
        thread.start(); // 启动线程
    }
}

TimerTask 的本质

TimerTask 是一个抽象类,你需要继承它并实现 run() 方法,这个方法包含了你想要定时执行的任务逻辑。

深入剖析 Java 定时器 Timer:源码级原理与避坑指南
public abstract class TimerTask implements Runnable {
    // 任务状态
    int state = VIRGIN;

    // 任务需要执行的时间
    long nextExecutionTime;

    // 执行周期,如果是一次性任务,则为 0
    long period = 0;

    // 任务执行体
    public abstract void run();
}

Timer 的 schedule 方法分析

Timer 提供了多种 schedule 方法,用于安排任务的执行时间。其中最常用的几种包括:

深入剖析 Java 定时器 Timer:源码级原理与避坑指南
  • schedule(TimerTask task, long delay):在 delay 毫秒后执行 task 一次。
  • schedule(TimerTask task, Date time):在指定的时间 time 执行 task 一次。
  • schedule(TimerTask task, long delay, long period):在 delay 毫秒后开始执行 task,以后每隔 period 毫秒重复执行一次。
  • schedule(TimerTask task, Date firstTime, long period):在指定的时间 firstTime 开始执行 task,以后每隔 period 毫秒重复执行一次。

这些 schedule 方法最终都会将 TimerTask 对象添加到内部的任务队列 TaskQueue 中,并唤醒 TimerThread 线程。

public void schedule(TimerTask task, long delay) {
    if (delay < 0)
        throw new IllegalArgumentException("Negative delay.");
    sched(task, System.currentTimeMillis()+delay, 0); // 调用 sched 方法
}

private void sched(TimerTask task, long time, long period) {
    synchronized(queue) {
        if (!thread.newTasksMayBeScheduled)
            throw new IllegalStateException("Timer already cancelled.");

        synchronized(task) {
            if (task.state != TimerTask.VIRGIN)
                throw new IllegalStateException("Task already scheduled or cancelled");
            task.nextExecutionTime = time; // 设置下次执行时间
            task.period = period; // 设置执行周期
            queue.add(task); // 添加到任务队列
            if (queue.getMin() == task) // 如果是队列中最早的任务,则唤醒线程
                queue.notify(); // 唤醒 TimerThread
        }
    }
}

实战避坑经验

  1. 避免长时间阻塞的任务:由于 Timer 使用单线程模型,长时间阻塞的任务会影响后续任务的执行。如果任务可能执行时间较长,建议使用 ScheduledExecutorService,它基于线程池,可以并行执行任务。
  2. 处理异常:在 TimerTaskrun() 方法中,一定要捕获并处理所有可能抛出的异常。如果 run() 方法抛出未捕获的异常,Timer 线程会终止,导致后续任务无法执行。
  3. 取消任务:不再需要的 TimerTask 应该及时取消,防止资源浪费。可以使用 TimerTask.cancel() 方法取消任务,使用 Timer.cancel() 方法取消整个 Timer
  4. 注意线程安全TimerTimerTask 本身是线程安全的,但是 run() 方法中访问的共享资源需要进行适当的同步,避免并发问题。
  5. 时区问题:在使用 schedule(TimerTask task, Date time) 方法时,要注意时区问题,确保指定的 Date 对象的时间是正确的。

代码示例:使用 Timer 调度任务

import java.util.Timer;
import java.util.TimerTask;

public class TimerExample {
    public static void main(String[] args) {
        Timer timer = new Timer();
        TimerTask task = new TimerTask() {
            @Override
            public void run() {
                System.out.println("Task executed at: " + System.currentTimeMillis());
            }
        };

        // 延迟 1 秒后执行任务
        timer.schedule(task, 1000);

        // 延迟 2 秒后执行任务,以后每隔 3 秒重复执行
        // timer.schedule(task, 2000, 3000);
    }
}

替代方案:ScheduledExecutorService

ScheduledExecutorService 是 Java 并发包中提供的更强大、更灵活的定时任务调度器。它基于线程池,可以并行执行任务,并提供了更多的调度选项,例如延迟执行、固定速率执行、固定延迟执行等。 在实际开发中,建议优先使用 ScheduledExecutorService

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class ScheduledExecutorServiceExample {
    public static void main(String[] args) {
        ScheduledExecutorService executor = Executors.newScheduledThreadPool(1); // 创建一个单线程的线程池

        Runnable task = () -> {
            System.out.println("Task executed at: " + System.currentTimeMillis());
        };

        // 延迟 1 秒后执行任务
        executor.schedule(task, 1, TimeUnit.SECONDS);

        // 延迟 2 秒后执行任务,以后每隔 3 秒重复执行
        executor.scheduleAtFixedRate(task, 2, 3, TimeUnit.SECONDS);

        // 注意:ExecutorService 的关闭需要谨慎处理,否则可能导致资源泄漏
        // executor.shutdown(); // 平滑关闭
    }
}

总结来说,java.util.Timer 是一个简单易用的定时任务调度器,但由于其单线程模型,在高并发、任务执行时间不确定的场景下,容易出现问题。建议在实际开发中,根据具体情况选择合适的定时任务调度器,例如 ScheduledExecutorService,并注意线程安全、异常处理等问题。深入理解 Java学习的 Timer 源码,能帮助我们更好地选择和使用这些工具。

深入剖析 Java 定时器 Timer:源码级原理与避坑指南

转载请注明出处: 键盘上的咸鱼

本文的链接地址: http://m.acea5.store/blog/917021.SHTML

本文最后 发布于2026-04-24 23:48:29,已经过了2天没有更新,若内容或图片 失效,请留言反馈

()
您可能对以下文章感兴趣
评论
  • 社恐患者 18 小时前
    ScheduledExecutorService 确实比 Timer 灵活多了,但是线程池的大小也要根据实际情况调整,不然也会有性能问题。
  • 彩虹屁大师 4 天前
    学习了,之前一直以为 Timer 是线程安全的,看来还是要注意 run 方法里的共享资源访问啊。
  • 打工人日记 5 天前
    ScheduledExecutorService 确实比 Timer 灵活多了,但是线程池的大小也要根据实际情况调整,不然也会有性能问题。
  • 豆腐脑 3 天前
    ScheduledExecutorService 确实比 Timer 灵活多了,但是线程池的大小也要根据实际情况调整,不然也会有性能问题。