JDK Timer 实现原理分析

说明

本文分析的是 JDK 7

Timer 基本用法

public static void main(String[] args) {
    Timer timer = new Timer();
    timer.scheduleAtFixedRate(new TimerTask() {
        
        @Override
        public void run() {
            System.out.println(new Date(this.scheduledExecutionTime()));
        }
        
    }, 500L, 1000L);
}

输出:

Mon Jul 02 14:34:20 CST 2018
Mon Jul 02 14:34:21 CST 2018
Mon Jul 02 14:34:22 CST 2018
Mon Jul 02 14:34:23 CST 2018
Mon Jul 02 14:34:24 CST 2018
Mon Jul 02 14:34:25 CST 2018

主要能够指定定时任务的初始延迟、间隔执行时间。

实现原理

首先画了一张示意图,能够说明 Timer 的基本原理。

大体上有 4 个类:

  • Timer:定时器主类
  • TimerTask:实现了 Runnable 的抽象类(run 仍为抽象方法,需要用户实现),定义了与 Timer 特有的任务状态(下一次执行时间 nextExecutionTime 和 执行时间间隔 period)
  • TimerThread:由 Timer 启动,里面有个无限循环的 mainLoop 方法,用来取出 TaskQueue 中最近需要执行的任务,判断是否需要执行。
  • TaskQueue:由 TimerTask 组成的小顶堆,其排序是根据 TimerTask 的 nextExecutionTime。

TimerTask 的状态

/**
 * The state of this task, chosen from the constants below.
 */
int state = VIRGIN;

/**
 * This task has not yet been scheduled.
 */
static final int VIRGIN = 0;

/**
 * This task is scheduled for execution.  If it is a non-repeating task,
 * it has not yet been executed.
 */
static final int SCHEDULED   = 1;

/**
 * This non-repeating task has already executed (or is currently
 * executing) and has not been cancelled.
 */
static final int EXECUTED    = 2;

/**
 * This task has been cancelled (with a call to TimerTask.cancel).
 */
static final int CANCELLED   = 3;

Timer 的两个方法

  • schedule
  • scheduleAtFixedRate

函数的定义如下:

public void schedule(TimerTask task, long delay, long period) {
    if (delay < 0)
        throw new IllegalArgumentException("Negative delay.");
    if (period <= 0)
        throw new IllegalArgumentException("Non-positive period.");
    sched(task, System.currentTimeMillis()+delay, -period);
}

public void scheduleAtFixedRate(TimerTask task, long delay, long period) {
    if (delay < 0)
        throw new IllegalArgumentException("Negative delay.");
    if (period <= 0)
        throw new IllegalArgumentException("Non-positive period.");
    sched(task, System.currentTimeMillis()+delay, period);
}

注意其中最大的区别,在于 schedule 调用 sched 函数时,将传入的 period 取反了。如果某次执行任务的开始时间延后了,那么此后的每次任务都会延迟。

If an execution is delayed for any reason (such as garbage collection or other background activity), subsequent executions will be delayed as well. In the long run, the frequency of execution will generally be slightly lower than the reciprocal of the specified period (assuming the system clock underlying Object.wait(long) is accurate). As a consequence of the above, if the scheduled first time is in the past, it is scheduled for immediate execution. 

而 scheduleAtFixedRate 则并不会这样。

If an execution is delayed for any reason (such as garbage collection or other background activity), two or more executions will occur in rapid succession to "catch up." In the long run, the frequency of execution will be exactly the reciprocal of the specified period (assuming the system clock underlying Object.wait(long) is accurate). 

其加入后的核心函数是 sched。参数 period

  • 0:重复任务的时间间隔(scheduleAtFixedRate)
  • =0:仅执行一次
  • <0:重复任务的时间间隔(schedule)
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.");

        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;
        }

        queue.add(task);
        if (queue.getMin() == task)
            queue.notify();
    }
}

TimerTask 核心方法

/**
 * The main timer loop.  (See class comment.)
 */
private void mainLoop() {
    while (true) {
        try {
            TimerTask task;
            boolean taskFired;
            synchronized(queue) {
                // Wait for queue to become non-empty
                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
                long currentTime, executionTime;
                task = queue.getMin();
                synchronized(task.lock) {
                    if (task.state == TimerTask.CANCELLED) {
                        queue.removeMin();
                        continue;  // No action required, poll queue again
                    }
                    currentTime = System.currentTimeMillis();
                    executionTime = task.nextExecutionTime;
                    if (taskFired = (executionTime<=currentTime)) {
                        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);
                        }
                    }
                }
                if (!taskFired) // Task hasn't yet fired; wait
                    queue.wait(executionTime - currentTime);
            }
            if (taskFired)  // Task fired; run it, holding no locks
                task.run();
        } catch(InterruptedException e) {
        }
    }
}

整体流程是:获取 queue 的锁,若 queue 不为空,则拿出第一个任务,获取任务的锁,若任务的状态正常且到执行时间,则执行任务;否则将其 wait 对应的时间。

其最核心的代码:

currentTime = System.currentTimeMillis();
executionTime = task.nextExecutionTime;
if (taskFired = (executionTime<=currentTime)) {
    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);
    }
}

费曼技巧:概述 Timer 的实现原理

Timer 是 JDK 自带的执行定时任务的工具类,用户能够指定延迟、任务执行的时间间隔。当 new 一个 Timer 时,会自动启动一个 TimerTask 线程,这个线程无限循环一个小顶堆的任务队列,每次取出最近需要执行的任务,如果符合条件则对该任务(小顶堆)做相应处理。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏difcareer的技术笔记

JNI实现源码分析【四 函数调用】正文0x01:dvmCallMethodV0x02:nativeFunc0x03: 何时赋值

有了前面的铺垫,终于可以说说虚拟机是如何调用JNI方法的了。JNI方法,对应Java中的native方法,所以我们跟踪对Native方法的处理即可。

11540
来自专栏函数式编程语言及工具

泛函编程(38)-泛函Stream IO:IO Process in action

  在前面的几节讨论里我们终于得出了一个概括又通用的IO Process类型Process[F[_],O]。这个类型同时可以代表数据源(Source)和数据终端...

21170
来自专栏老码农专栏

在JavaScript中仿真Java的enum

11820
来自专栏成长道路

scala的maven项目读取配置文件

scala的maven项目读取src/main/resources目录下的资源文件该如何读取呢? 下面提供一种默认的和一种自定义的: ? 对于applicati...

51200
来自专栏desperate633

设计模式之访问者模式(visitor模式)引入访问者模式visitor模式的实例visitor模式分析

Visitor是访问者的意思。 数据结构中保存着元素。一般我们需要对元素进行处理,那么处理元素的代码放在哪里呢?最显然的方法就是放在数据结构的类中,在类中添加...

9530
来自专栏云霄雨霁

Java--通配符类型

19940
来自专栏一个会写诗的程序员的博客

《Kotlin 程序设计》第十二章 Kotlin的多线程

Kotlin 1.1 introduced coroutines, a new way of writing asynchronous, non-blockin...

14110
来自专栏PHP在线

PHP7新特性介绍

文内容根据PHP发布时的 new files 而来,链接地址 : PHP 7 new 特性一览 Added??operator Added <=> opera...

38460
来自专栏java、Spring、技术分享

深入分析Spring Type Conversion数据转换

  Spring 3 推出了全新的数据类型之间的转换以替换PropertyEditor转换模式,但网上并没有很好的源码解读。Spring MVC是如何用这套转换...

10620
来自专栏Android Note

Kotlin中run, with, let, also and apply使用方法

11520

扫码关注云+社区

领取腾讯云代金券