美文网首页
Timer机制源码浅析

Timer机制源码浅析

作者: 点先生在这 | 来源:发表于2019-03-27 15:41 被阅读0次

    逼逼两句

    Q:定时、延时任务有几种方式可以实现?
    A:Handler、Timer、ScheduledThreadPool、AlarmManager

    Handler机制大家应该都烂熟于心了,今天我来讲讲Timer这个不常被问到的定时器。
    改日再说线程池,预计是周日。

    Timer机制包含了四个主要核心类:Timer,TaskQueue,TimerThread,TimerTask。咱们一个个来了解。

    Timer

    Timer类加载时创建新的任务队列,新的定时器线程。并将两个绑定起来。

    public class Timer {
        private final TaskQueue queue = new TaskQueue();
        private final TimerThread thread = new TimerThread(queue);
    

    初始化Timer

    反正就是给thread设置名字,或者设置是否是守护线程,最后开启线程;这个thread,就是TimerThread。

    调用这四个方法可以执行定时任务延时任务周期执行任务

    这两个方法与上面最后两个方法很类似,不同的地方在于sched()的最后一个参数,传入当前值或是相反数值,这里的具体影响后面会介绍到。sched()的核心代码为:

    private void sched(TimerTask task, long time, long period) {
           //其他逻辑
           synchronized(queue) {
              //其他逻辑
              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();
            }
        }
    

    主要就是将初始化后的task进行赋值,然后加入队列。
    至此Timer里面就还有两个方法没说到。

    public void cancel(){ 清空队列,通知队列 }
    public int purge(){ 将队列中状态为“CANCELLED”的任务移除,并重排序队列。 }
    

    TaskQueue

    任务队列实际上就是一个TimerTask的大小为128的数组。size表示队列中的任务数。
    其他的就是一些操作此数组的方法

    int size() { 获取当前任务数 }
    void add(TimerTask task){ 添加任务到数组,并 fixUp(size),第一个元素的位置为1,非0。 }
    TimerTask getMin(){ 得到最近的一个任务 }
    TimerTask get(int i){ 得到i元素 }
    void removeMin(){ 移除最近的一个任务,并 fixDown(1) }
    void quickRemove(int i){ 快速移速某个任务,不重排序 }
    void rescheduleMin(long newTime){ 重新设置最近任务的执行时间,并 fixDown(1) }
    boolean isEmpty(){ 判断队列是否为空 }
    void clear(){ 清空队列 }
    void fixUp(int k){ 排序方法1 }
    void fixDown(int k){ 排序方法2 }
    void heapify(){ 排序方法3 }
    

    三种排序方式不再此深探究。在此留下一个疑问,为何第一个任务添加进来给的位置是1,非0;

    TimerThread

    class TimerThread extends Thread {
        boolean newTasksMayBeScheduled = true;  
        private TaskQueue queue
        TimerThread(TaskQueue queue) {
            this.queue = queue;
        }
        public void run() {
            try { mainLoop();
            } finally {
                synchronized(queue) {
                    newTasksMayBeScheduled = false;
                    queue.clear(); 
                }
            }
        }
    
    1. TimerThread是一个Thread。
    2. 初始化的时候绑定对应TaskQueen。
    3. TimerThread正常运行时,就一直循环取队列消息执行任务。当线程被杀死,或者其他异常出现时候,便会清空队列。
      tips:newTasksMayBeScheduled 是标志这当前是否对定时器对象保持引用。当队列中不再有任务,则为真。

    最后来看看mainLoop()中的核心代码:

    private void mainLoop() {
            while (true) {
                try {
                    synchronized(queue) {
                        //其他逻辑
                        long currentTime, executionTime;
                        task = queue.getMin();
                        synchronized(task.lock) {
                            //其他逻辑
                            currentTime = System.currentTimeMillis();
                            executionTime = task.nextExecutionTime;
                            if (taskFired = (executionTime<=currentTime)) {
                                if (task.period == 0) { 
                                    queue.removeMin();
                                    task.state = TimerTask.EXECUTED;
                                } else {
                                    queue.rescheduleMin(
                                    task.period<0 ? currentTime   - task.period
                                                    : executionTime + task.period);
                                }
                            }
                        }
                        if (!taskFired) 
                            queue.wait(executionTime - currentTime);
                    }
                    if (taskFired)  
                        task.run();
                } catch(InterruptedException e) {
                }
            }
        }
    

    线程运行起来之后,就一直在取最近的消息对比当前时间,执行时间到了,就看是否是一次性任务。如果是一次性任务,就更改任务状态。如果是周期任务,就把给任务设置新的执行时间再入队列。如果一开始执行时间就没到,就wait当前队列。最后根据执行时间是否到达,执行取出来的最近任务。

    tips:周期任务重置时间时,有两种时间,当period<0时currentTime - task.period ,当period>0时executionTime + task.period。根据Timer中sched()和scheduleAtFixedRate()的区别能推断出,前者代码表示,当前任务执行完之后,再进入period时间。后这代码表示,当前任务执行开始的时,就进入period时间。

    TimerTask

    TimerTask是个抽象类,实现了Runnable接口。内部拥有四个属性,三个方法。

    public abstract class TimerTask implements Runnable {
         final Object lock = new Object(); //对象锁,用于维护线程安全;
         int state = VIRGIN;//状态值
         long nextExecutionTime;//下一次执行的时间
         long period = 0;//周期时间
    
         static final int VIRGIN = 0;
         static final int SCHEDULED   = 1;
         static final int EXECUTED    = 2;
         static final int CANCELLED   = 3;
    }
    

    VIRGIN :初始化默认值,表达此任务还没被加入执行队列。
    SCHEDULED :任务被安排准备执行,已加入执行队列
    EXECUTED : 任务正在执行或者已经执行,还没被取消。
    CANCELLED :任务已经被取消

    public abstract void run();
    
     public boolean cancel() {
            synchronized(lock) {
                boolean result = (state == SCHEDULED);
                state = CANCELLED;
                return result;
            }
        }
    
     public long scheduledExecutionTime() {
            synchronized(lock) {
                return (period < 0 ? nextExecutionTime + period
                                   : nextExecutionTime - period);
            }
        }
    

    继承TimerTask或者匿名内部类创建都可以实现执行定时任务,将要执行的动作写在run()里面即可。
    cancel()返回当前任务状态值是否是SCHEDULED,再将其状态值改成CANCELLED。
    scheduledExecutionTime()返回的是下一次执行的时间。

    Timer与Handler

    • Timer机制的结构跟Handler类似,具体处理不一样,但都分为四大结构。
    • Timer、Handler:主控器
    • TimerTask、Message:消息/任务
    • TimerQueue、MessageQueue:消息/任务队列
    • TimerThread、Looper:循环取消息/任务
    优缺点 Handler Timer
    执行同一个非周期任务 只需要再发一次消息 需要创建新的TimerTask
    通信 线程间通信灵活 TimerTask执行在子线程中
    可靠性 周期执行任务比较可靠 周期执行任务不可靠(下面解释)
    内存泄漏 容易泄漏 容易泄漏
    内存消耗 相对较大
    灵活性 依赖looper,不灵活 Timer不依赖其他类

    Timer执行的周期任务容易被自身干扰。(当耗时任务在sched()中执行时候,会大大延迟下一次任务的执行;当耗时任务需要操作同一个对象在scheduleAtFixedRate()中执行的时候,拿不到任务对象,等待上一次的任务释放锁。)

    小结

    Handler适合大多数场景,且好处理。
    Timer只适合执行耗时比较少的重复任务。
    难怪Timer相关文章热度这么低,看完源码才知道,是个小辣鸡。这两天时间算是浪费了。

    最后希望大家多多关注我们的博客团队:天星技术博客https://juejin.im/user/5afa539751882542aa42e5c5

    相关文章

      网友评论

          本文标题:Timer机制源码浅析

          本文链接:https://www.haomeiwen.com/subject/hpwgvqtx.html