Timer机制源码浅析

栏目: Java · 发布时间: 6年前

内容简介:Q:定时、延时任务有几种方式可以实现?A:Handler、Timer、ScheduledThreadPool、AlarmManagerHandler机制大家应该都烂熟于心了,今天我来讲讲Timer这个不常被问到的定时器。 改日再说线程池,预计是周日。

Q:定时、延时任务有几种方式可以实现?

A:Handler、Timer、ScheduledThreadPool、AlarmManager

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

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

Timer机制源码浅析

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

Timer机制源码浅析

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

Timer机制源码浅析

这两个方法与上面最后两个方法很类似,不同的地方在于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

Timer机制源码浅析

任务队列实际上就是一个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相关文章热度这么低,看完源码才知道,是个小辣鸡。这两天时间算是浪费了。

Timer机制源码浅析

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


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Algorithms to Live By

Algorithms to Live By

Brian Christian、Tom Griffiths / Henry Holt and Co. / 2016-4-19 / USD 30.00

A fascinating exploration of how insights from computer algorithms can be applied to our everyday lives, helping to solve common decision-making problems and illuminate the workings of the human mind ......一起来看看 《Algorithms to Live By》 这本书的介绍吧!

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具