做者:点先生 时间:2019.3.27java
Q:定时、延时任务有几种方式能够实现?
A:Handler、Timer、ScheduledThreadPool、AlarmManagerapi
Handler机制你们应该都烂熟于心了,今天我来说讲Timer这个不常被问到的定时器。 改日再说线程池,预计是周日。数组
Timer机制包含了四个主要核心类:Timer,TaskQueue,TimerThread,TimerTask。我们一个个来了解。安全
Timer类加载时建立新的任务队列,新的定时器线程。并将两个绑定起来。oop
public class Timer {
private final TaskQueue queue = new TaskQueue();
private final TimerThread thread = new TimerThread(queue);
}
复制代码
初始化Timerui
反正就是给thread设置名字,或者设置是不是守护线程,最后开启线程;这个thread,就是TimerThread。this
调用这四个方法能够执行定时任务、延时任务、周期执行任务。spa
这两个方法与上面最后两个方法很相似,不一样的地方在于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里面就还有两个方法没说到。code
public void cancel(){ 清空队列,通知队列 }
public int purge(){ 将队列中状态为“CANCELLED”的任务移除,并重排序队列。 }
复制代码
任务队列实际上就是一个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;
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();
}
}
}
复制代码
最后来看看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是个抽象类,实现了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()返回的是下一次执行的时间。
优缺点 | Handler | Timer |
---|---|---|
执行同一个非周期任务 | 只须要再发一次消息 | 须要建立新的TimerTask |
通讯 | 线程间通讯灵活 | TimerTask执行在子线程中 |
可靠性 | 周期执行任务比较可靠 | 周期执行任务不可靠(下面解释) |
内存泄漏 | 容易泄漏 | 容易泄漏 |
内存消耗 | 小 | 相对较大 |
灵活性 | 依赖looper,不灵活 | Timer不依赖其余类 |
Timer执行的周期任务容易被自身干扰。(当耗时任务在sched()中执行时候,会大大延迟下一次任务的执行;当耗时任务须要操做同一个对象在scheduleAtFixedRate()中执行的时候,拿不到任务对象,等待上一次的任务释放锁。)
Handler适合大多数场景,且好处理。 Timer只适合执行耗时比较少的重复任务。 难怪Timer相关文章热度这么低,看完源码才知道,是个小辣鸡。这两天时间算是浪费了。
最后但愿你们多多关注咱们的博客团队:天星技术博客https://juejin.im/user/5afa539751882542aa42e5c5