通过源码剖析,咱们能够更深刻的理解其底层原理。
对于JDK自带的定时器,次要波及TimerTask类、Timer类、TimerQueue类、TimerThread类,其中TimerQueue和TimerThread类与Timer类位于同一个类文件,由Timer外部调用。
先画上一张图,形容一下Timer的大抵模型,Timer的模型很容易了解,即工作退出到工作队列中,由工作解决线程循环从工作队列取出工作执行:
一、TimerTask
TimerTask是一个工作抽象类,实现了Runnable接口,是可被线程执行的。
1. 工作状态
在TimerTask中定义了对于工作状态的常量字段:
// 未调度状态static final int VIRGIN = 0;// 工作已调度,但未执行static final int SCHEDULED = 1;// 若是一次性工作示意已执行;可反复执行工作,该状态有效static final int EXECUTED = 2;// 工作被勾销static final int CANCELLED = 3;
当一个TimerTask对象创立后,其初始状态为VIRGIN;
当调用Timer的schedule办法调度了此TimerTask对象后,其状态变更为SCHEDULED;
如果TimerTask是一次性工作,此工作执行后,状态将变为EXECUTED,可反复执行工作执行后状态不变;
当中途调用了TimerTask.cancel办法,该工作的状态将变为CANCELLED。
2. 工作属性阐明
TimerTask中,有如下成员变量:
// 用于加锁管制多线程批改TimerTask外部状态final Object lock = new Object();// 工作状态,初始状态为待未调度状态int state = VIRGIN;// 工作的下一次执行工夫点long nextExecutionTime;// 工作执行的工夫距离。负数示意固定速率;正数示意固定时延;0示意只执行一次long period = 0;
3. 工作办法阐明
TimerTask中有三个办法:
- run:实现了Runnable接口,创立TimerTask须要重写此办法,编写工作执行代码
- cancel:勾销工作
- scheduledExecutionTime:计算执行工夫点
3.1. Cancel办法
cancel办法的实现代码:
public boolean cancel() { synchronized(lock) { boolean result = (state == SCHEDULED); state = CANCELLED; return result; }}
在cancel办法内,应用synchronized加锁,这是因为Timer外部的线程会对TimerTask状态进行批改,而调用cancel办法个别会是另外一个线程。
为了防止线程同步问题,cancel在批改状态前进行了加锁操作。
调用cancel办法将会把工作状态变更为CANCELLED状态,即工作勾销状态,并返回一个布尔值,该布尔值示意此工作之前是否已是SCHEDULED 已调度状态。
3.2. scheduledExecutionTime办法
scheduledExecutionTime办法实现:
public long scheduledExecutionTime() { synchronized(lock) { return (period < 0 ? nextExecutionTime + period : nextExecutionTime - period); }}
该办法返回此工作的下次执行工夫点。
二、Timer
剖析Timer源代码,Timer在外部持有了两个成员变量:
private final TaskQueue queue = new TaskQueue();private final TimerThread thread = new TimerThread(queue);
TaskQueue是工作队列,TimerThread是工作解决线程。
1. sched办法
无论是应用schedule还是scheduleAtFixedRate办法来调度工作,Timer外部最初都是调用sched办法进行解决。
public void schedule(TimerTask task, Date time) { sched(task, time.getTime(), 0); // 一次性工作,period为0}public void schedule(TimerTask task, long delay) { ... sched(task, System.currentTimeMillis()+delay, 0); // 一次性工作,period为0}public void schedule(TimerTask task, long delay, long period) { ... sched(task, System.currentTimeMillis()+delay, -period); // 固定延时模式,-period}public void schedule(TimerTask task, Date firstTime, long period) { ... sched(task, firstTime.getTime(), -period); // 固定延时模式,-period}public void scheduleAtFixedRate(TimerTask task, long delay, long period) { ... sched(task, System.currentTimeMillis()+delay, period); // 固定速率模式,period为正}public void scheduleAtFixedRate(TimerTask task, Date firstTime, long period) { ... sched(task, firstTime.getTime(), period); // 固定速率模式,period为正}
sched办法外围代码:
private void sched(TimerTask task, long time, long period) { ... // 加锁,防止内部其余线程同时调用cancel,同时拜访queue产生线程同步问题 synchronized(queue) { // 如果线程已终止,抛出异样 if (!thread.newTasksMayBeScheduled) throw new IllegalStateException("Timer already cancelled."); // 加锁,防止多线程拜访同一个工作产生线程同步问题 synchronized(task.lock) { // task的状态必须为VIRGIN,否则认为曾经退出调度或者曾经勾销了,防止反复的调度 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(); }}
2. cancel办法
cancel办法个别是由内部其余线程调用,而Timer外部的线程也会对工作队列进行操作,因而加锁。
public void cancel() { synchronized(queue) { // 批改线程的循环执行标记,令线程可能终止 thread.newTasksMayBeScheduled = false; // 清空工作队列 queue.clear(); // 唤醒线程 queue.notify(); }}
3. purge办法
当通过TimerTask.cancel将工作勾销后,Timer的工作队列还援用着此工作,Timer只有到了要执行时才会移除,其余时候并不会主动将此工作移除,须要调用purge办法进行清理。
public int purge() { int result = 0; synchronized(queue) { // 遍历队列,将CANCELLED状态的工作从工作队列中移除 for (int i = queue.size(); i > 0; i--) { if (queue.get(i).state == TimerTask.CANCELLED) { queue.quickRemove(i); result++; } } // 如果移除工作数不为0,触发从新排序 if (result != 0) queue.heapify(); } // 返回移除工作数 return result; }
三、TaskQueue
TaskQueue是Timer类文件中封装的一个队列数据结构,外部默认是一个长度128的TimerTask数组,当工作退出时,检测到数组将满将会主动扩容1倍,并对数组元素依据下次执行工夫nextExecutionTime按工夫从近到远进行排序。
void add(TimerTask task) { // 检测数组长度,若不够则进行扩容 if (size + 1 == queue.length) queue = Arrays.copyOf(queue, 2*queue.length); // 工作入队 queue[++size] = task; // 排序 fixUp(size);}
fixUp办法实现:
private void fixUp(int k) { while (k > 1) { int j = k >> 1; if (queue[j].nextExecutionTime <= queue[k].nextExecutionTime) break; TimerTask tmp = queue[j]; queue[j] = queue[k]; queue[k] = tmp; k = j; }}
TaskQueue中除了fixUp办法外还有一个fixDown办法,这两个其实就是堆排序算法,在算法专题中再进行具体介绍,只有记住他们的工作就是按工夫从近到远进行排序,最近的工作排在队首即可。
private void fixDown(int k) { int j; while ((j = k << 1) <= size && j > 0) { if (j < size && queue[j].nextExecutionTime > queue[j+1].nextExecutionTime) j++; // j indexes smallest kid if (queue[k].nextExecutionTime <= queue[j].nextExecutionTime) break; TimerTask tmp = queue[j]; queue[j] = queue[k]; queue[k] = tmp; k = j; }}void heapify() { for (int i = size/2; i >= 1; i--) fixDown(i);}
四、TimerThread
TimerThread的外围代码位于mainLoop办法:
private void mainLoop() { // 死循环,从队列取工作执行 while (true) { try { TimerTask task; boolean taskFired; // 对工作队列加锁 synchronized(queue) { // 如果队列中没有工作,则进入期待,newTasksMayBeScheduled是线程运行标记位,为false时将退出循环 while (queue.isEmpty() && newTasksMayBeScheduled) queue.wait(); // 如果工作队列是空的还执行到这一步,阐明newTasksMayBeScheduled为false,退出循环 if (queue.isEmpty()) break; long currentTime, executionTime; // 从队列获得最近的工作 task = queue.getMin(); // 加锁 synchronized(task.lock) { // 如果工作状态是已勾销,则移除该工作,从新循环取工作 if (task.state == TimerTask.CANCELLED) { queue.removeMin(); continue; } // 以后工夫 currentTime = System.currentTimeMillis(); // 工作的执行工夫点 executionTime = task.nextExecutionTime; // 如果执行工夫点早于或等于以后工夫,即过期/工夫到了,则触发工作执行 if (taskFired = (executionTime<=currentTime)) { // 如果工作period=0,即一次性工作 if (task.period == 0) { // 从队列移除一次性工作 queue.removeMin(); // 工作状态变更为已执行 task.state = TimerTask.EXECUTED; } else { // 可反复执行工作,从新进行调度,period<0是固定时延,period>0是固定速率 queue.rescheduleMin( task.period<0 ? currentTime - task.period // 计算下次执行工夫 : executionTime + task.period); } } } // taskFired为false即工作尚未到执行工夫点,进行期待,等待时间是 执行工夫点 - 以后工夫点 if (!taskFired) queue.wait(executionTime - currentTime); } // taskFired为true示意已触发,执行工作 if (taskFired) task.run(); } catch(InterruptedException e) { } }}