内容简介:Q:定时、延时任务有几种方式可以实现?A:Handler、Timer、ScheduledThreadPool、AlarmManagerHandler机制大家应该都烂熟于心了,今天我来讲讲Timer这个不常被问到的定时器。 改日再说线程池,预计是周日。
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(); } } } 复制代码
- TimerThread是一个Thread。
- 初始化的时候绑定对应TaskQueen。
- 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
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Spark SQL内核剖析
朱锋、张韶全、黄明 / 电子工业出版社 / 2018-8 / 69.00元
Spark SQL 是 Spark 技术体系中较有影响力的应用(Killer application),也是 SQL-on-Hadoop 解决方案 中举足轻重的产品。《Spark SQL内核剖析》由 11 章构成,从源码层面深入介绍 Spark SQL 内部实现机制,以及在实际业务场 景中的开发实践,其中包括 SQL 编译实现、逻辑计划的生成与优化、物理计划的生成与优化、Aggregation 算......一起来看看 《Spark SQL内核剖析》 这本书的介绍吧!