Java中Timer类实现延迟任务源码分析(JDK11)

75 阅读4分钟

这篇文章会从实现的原理和源码介绍一下Timer类是如何实现延时队列的。

实现原理

首先我们先不看源码,先介绍一下Timer实现延时任务的逻辑

  • 我们在实例化Timer类的时候,Timer内部默认初始化一个TimerThread的线程
  • 然后给这个线程初始化一个TaskQueue,TaskQueue内部维护者一个TimerTask的集合,也就是TimerTask[]
  • 实例化的时候,Timer会在构造函数中,调用TimerThread的start()方法,直接启动线程
  • 启动线程以后,会检查TaskQueue是否为空以及判断是否能继续添加任务,如果为空且可以添加任务,则调用TaskQueue的wait()方法,阻塞掉TimerThread
  • 当给TaskQueue添加值的时候,会调用TaskQueue.notify()方法,唤醒TimerThread线程
  • TimerThread根据传入的延迟时间,通过调用TaskQueue.wate(time),来实现延时执行task

上面就是Timer的实例化流程,那么Timer实现延迟队列的核心逻辑就是,Timer在实例化的时候,先启动一个TimerThread线程,线程内部自循环;通过TaskQueue是否有值,判断TimerThread是否需要等待;给TaskQueue添加值的时候,唤醒TimerThread,让它去执行TaskQueue中最早的task

Timer内部的主要属性

public class Timer {
    /**
     * The timer task queue.  This data structure is shared with the timer
     * thread.  The timer produces tasks, via its various schedule calls,
     * and the timer thread consumes, executing timer tasks as appropriate,
     * and removing them from the queue when they're obsolete.
     */
     // 记录传入的任务
    private final TaskQueue queue = new TaskQueue();

    /**
     * The timer thread.
     */
     // 默认创建的线程
    private final TimerThread thread = new TimerThread(queue);
    // 余下代码省略
    // ......
}

Timer的构造函数

    public Timer() {
        this("Timer-" + serialNumber());
    }

    public Timer(String name) {
        thread.setName(name);
        thread.start();
    }

从上面可以看到,直接调用了thread.start()方法启动一个线程,那么启动了线程以后,当然就是执行线程中的run方法,我们下面看TimerThread的run方法

TimerThread#run()

public void run() {
        try {
            mainLoop();
        } finally {
            // Someone killed this Thread, behave as if Timer cancelled
            synchronized(queue) {
                newTasksMayBeScheduled = false;
                queue.clear();  // Eliminate obsolete references
            }
        }
    }

可以看到,核心逻辑就在mainLoop()里

mainLoop()

下面解释一下主要的执行逻辑,会用*作为标记

private void mainLoop() {
        while (true) {
            try {
                TimerTask task;
                boolean taskFired;
                synchronized(queue) {
                    // Wait for queue to become non-empty
                    // *作者的注释说的很明显了,等待queue队列不为空
                    while (queue.isEmpty() && newTasksMayBeScheduled)
                        // *到这里线程就阻塞掉了,需要等待唤醒
                        queue.wait();
                    if (queue.isEmpty())
                        break; // Queue is empty and will forever remain; die

                    // Queue nonempty; look at first evt and do the right thing
                    // *下面的逻辑就是queue不为空时的操作
                    long currentTime, executionTime;
                    task = queue.getMin();
                    synchronized(task.lock) {
                        // *去掉TimerTask.CANCELLED状态的任务
                        if (task.state == TimerTask.CANCELLED) {
                            queue.removeMin();
                            continue;  // No action required, poll queue again
                        }
                        // *当前时间和任务执行时间,每一个task被加入queue的时候,都是根据System.currentTimeMillis()+time生成的
                        // *task.nextExecutionTime = System.currentTimeMillis()+time
                        currentTime = System.currentTimeMillis();
                        executionTime = task.nextExecutionTime;
                        // *还没有到执行的时间
                        if (taskFired = (executionTime<=currentTime)) {
                            // *task.period代表循环执行的延时时间如果为0就只执行一次
                            // 可以理解为任务执行一次以后,等待task.period毫秒后再执行一次,反复循环
                            if (task.period == 0) { // Non-repeating, remove
                                queue.removeMin();
                                task.state = TimerTask.EXECUTED;
                            } else { // Repeating task, reschedule
                                queue.rescheduleMin(
                                  task.period<0 ? currentTime   - task.period
                                                : executionTime + task.period);
                            }
                        }
                    }
                    // *这里就是延迟执行的逻辑,其实就是wati(对应的时间)
                    // 
                    if (!taskFired) // Task hasn't yet fired; wait
                        queue.wait(executionTime - currentTime);
                }
                if (taskFired)  // Task fired; run it, holding no locks
                    // *执行自己定义的TimerTask
                    task.run();
            } catch(InterruptedException e) {
            }
        }
    }
}

上面的代码,我们看到了核心逻辑是queue.wait(),先判断queue是否为空,为空的话等待;再判断是否到了执行时间,没到的话queue.wait(executionTime - currentTime)等待时间到。那么问题来了,第一次queue为空的等待,是怎么被唤醒的?

Timer#schedule()

我们使用Tiemr的时候,一般是先创建一个Timer,再添加一个TimerTasker那么唤醒的逻辑,就在schedule()里面

先看主流程:

public class TimerTaskTest {

    public static void main(String[] args) {
        //定义一个TimerTask
        TimerTask timerTask = new TimerTask() {
            /**
             * The action to be performed by this timer task.
             */
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + " TimerTask is running");
            }
        };
        // 创建一个Timer
        Timer timer = new Timer();
        // 调用schedule
        timer.schedule(timerTask, 1000);
    }
}

我们知道new Timer()的时候,就有一个TimerThread被创建了,并且在等待queue不为空;schedule()就是把任务加入queue,并且唤醒TimerThread

public void schedule(TimerTask task, long delay) {
        if (delay < 0)
            throw new IllegalArgumentException("Negative delay.");
        sched(task, System.currentTimeMillis()+delay, 0);
    }
private void sched(TimerTask task, long time, long period) {
        if (time < 0)
            throw new IllegalArgumentException("Illegal execution time.");

        // Constrain value of period sufficiently to prevent numeric
        // overflow while still being effectively infinitely large.
        if (Math.abs(period) > (Long.MAX_VALUE >> 1))
            period >>= 1;

        synchronized(queue) {
            if (!thread.newTasksMayBeScheduled)
                throw new IllegalStateException("Timer already cancelled.");

            // *设置TimerTask的属性,让TimerThread调用的时候使用
            synchronized(task.lock) {
                if (task.state != TimerTask.VIRGIN)
                    throw new IllegalStateException(
                        "Task already scheduled or cancelled");
                task.nextExecutionTime = time;
                task.period = period;
                task.state = TimerTask.SCHEDULED;
            }
            // *添加task
            queue.add(task);
            if (queue.getMin() == task)
                // 唤醒TimerThread继续执行
                queue.notify();
        }
    }

看完核心逻辑以后,是不是发现一个问题,这个Timer无法优雅的cancel,没发配置queue执行完以后,自行cancel

Timer 类本身在Java 5及之后的版本已经被推荐使用 ScheduledExecutorService 替代,因为 ScheduledExecutorService 提供了更灵活的调度和取消任务的机制,同时支持更多的任务执行策略。所以只能学习一下上面的思想,真的用的话,还是用ScheduledExecutorService吧