java源码-定时工具类Timer的原理

Timer是jdk提供的定时任务执行器.

我们本文主要分析一下,Timer的工作原理.

一、Timer的使用方法

1.1.代码

package com.study.jdk.sched;
import java.util.Timer;
import java.util.TimerTask;

public class TimerTest {

    public static void main(String[] args) {
        Timer timer = new Timer();
        long begin = System.currentTimeMillis();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("执行定时逻辑:"+(System.currentTimeMillis()-begin));
            }
        },1000,3000);

    }
}
结果:
执行定时逻辑:1002
执行定时逻辑:4006
执行定时逻辑:7011
执行定时逻辑:10014
执行定时逻辑:13018
执行定时逻辑:16022
执行定时逻辑:19027
执行定时逻辑:22031
执行定时逻辑:25034
执行定时逻辑:28039
执行定时逻辑:31043
执行定时逻辑:34047
执行定时逻辑:37052

二、Timer的原理和源码分析

2.1.原理图

在这里插入图片描述

Timer原理:

  1. Timer内部维护了一个优先队列,存储提交的TimerTask,TimerTask即用户要执行的定时逻辑,实现了Runnable接口
  2. 在创建一个Timer的时候,会创建一个TimerThread,可以设置以守护线程方式运行
  3. ThreadThread 以while(true)的方式,从优先队列queue中获取TimerTask,如果是周期性的,再重新将设置当前TimeTask的时间,优先队列执行fix逻辑,重新设置task位置
  4. 执行当前任务

2.2.代码分析

2.2.1.添加任务

Timer:

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


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


sched方法流程:

  1. 排他占有当前队列,这说明,在添加任务期间,无法从queue获取元素,即无法执行其他任务
  2. 判断当前Timer是否可用
  3. 获取当前任务的锁
  4. 判断当前任务状态,如果已经在调度状态或者取消,就不能添加,即添加过一次,就不能再添加
    1. 设置下次执行的时间配置
      1. 下次执行时间
      2. 周期时间(如果是周期执行的话)
      3. 任务状态,设置为SCHEDULED
  5. 添加到queue中
  6. 如果当前任务在是第一个待执行的,唤醒queue后面会说下,为什么要执行这一步唤醒

2.2.2.执行任务

Timer.TimerThread


class TimerThread extends Thread {
    /**
     * This flag is set to false by the reaper to inform us that there
     * are no more live references to our Timer object.  Once this flag
     * is true and there are no more tasks in our queue, there is no
     * work left for us to do, so we terminate gracefully.  Note that
     * this field is protected by queue's monitor!
     */
    boolean newTasksMayBeScheduled = true;

    /**
     * Our Timer's queue.  We store this reference in preference to
     * a reference to the Timer so the reference graph remains acyclic.
     * Otherwise, the Timer would never be garbage-collected and this
     * thread would never go away.
     */
    private TaskQueue queue;

    TimerThread(TaskQueue queue) {
        this.queue = queue;
    }

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

    /**
     * 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) {
            }
        }
    }
}


  public void cancel() {
        synchronized(queue) {
            thread.newTasksMayBeScheduled = false;
            queue.clear();
            queue.notify();  // In case queue was already empty.
        }
    }

执行TimerTask的逻辑主要在mainLoop里面:

  1. 首先排他占有queue

  2. 第一个等待

    1. 当前任务队列为空和newTasksMayBeScheduled,该值默认为true
    2. 上面添加完任务,执行queue.notify,可能唤醒的是这个地方,如果当前当前任务是第一个添加的
  3. 接下来,再次判断queue是否为空,如果为空,结束循环.因为这时候newTasksMayBeScheduled已经被设置为false了,

    1. 当执行Timer.cancel方法时newTasksMayBeScheduled会被设置成false,并将当前队列清空,
  4. 获取下一个需要执行的task,即优先队列的第一个元素

  5. 获取当前task的锁

    1. 判断当前task状态,如果task已经被取消,从队列中移除,循环执行第一步操作
    2. 获取当前时间戳:currentTime,获取task执行的时间戳nextExecutionTime
    3. 如果nextExecutionTime 小于currentTime,则说明,当前任务需要立刻执行
      1. 如果当前任务不是周期性的,从队列移除
      2. 否则: 重新设置下次任务执行时间,加入优先队列queue
    4. 判断是否立刻执行
      1. 是:执行定时任务逻辑
      2. 否:queue.wait
        1. 如果在这里wait,当有新任务加入,并且新任务比该任务要提前执行,说明新加入的任务已经位于优先队列一个元素.
        2. 在上面的sched方法中,会执行queue.notify方法,唤醒锁,则该处的queue.wait方法,会被唤醒,继续执行while循环,获取第一个task,并执行它

我们从mainLoop方法,可以发现该逻辑有点想生产者-消费者模型:

  1. sched:增加新的任务
  2. mainLoop:消费任务
  3. 每次执行循环,都取优先队列,第一个任务.

三、注意事项

我们从上面代码分析可以得到以下信息:

  1. Timer执行定时逻辑是单线程的
  2. 如果一个定时逻辑TimerTask执行时间过长,会阻塞后面的执行,所以一定要避免,TimerTask里面的逻辑不会长时间阻塞
  3. 调用Timer.cancel会清空当前所有待执行的task,并关闭timer.
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序猿老徐

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值