像作者所说,本博文将总结如下内容
1、如何实现指定时间执行任务;2、如果实现按指定周期执行任务(定时器的常见需求)
Timer用于设置计划任务,但封装任务的类型是TimerTask类,执行任务的代码要放入TimerTask子类中,你猜为何
schedule(***)*情况下指定任务执行
//delay后调度一个task、一次
public void schedule(TimerTask task, long delay);
//源码
public void schedule(TimerTask task, long delay) {
if (delay < 0)
throw new IllegalArgumentException("Negative delay");
/**
* 参数二:如果传入Date,对象.getTime即可
* 参数三:0,时间片
*/
sched(task,System.currentTimeMillis()+delay,0);
}
//在指定的时间点time上调度一个task,一次
public void schedule(TimerTask task, Date time);
//delay后开始调一个task,每次调完最少等待period(ms)才开始调度
public void schedule(TimerTask task, long delay, long period);
public void schedule(TimerTask task, long delay, long period) {
if (delay < 0)
throw new IllegalArgumentException("Negative delay.");
if (period <= 0)
throw new IllegalArgumentException("Non-positive period.");
/*参数三:-取反*/
sched(task, System.currentTimeMillis()+delay, -period);
}
//同上,delay为第一次调度的时间:当前时间+时间片 实际时间 根据情况变动但是可能会被少调度*次
public void schedule(TimerTask task, Date firstTime, long period);
//在delay后开始调task,每经过period再次调度:计算出现在应该执行的时间+时间片 理论时间 时间不变减少漏掉调度的情况
public void scheduleAtFixedRate(TimerTask task, long delay, long period);
//同上,第一次调度的时间设为firstTime
public void scheduleAtFixedRate(TimerTask task, Date firstTime,long period);
//和上面的第二个源码并无上面差别
public void scheduleAtFixedRate(TimerTask task, long delay, long period) {
if (delay < 0)
throw new IllegalArgumentException("Negative delay.");
if (period <= 0)
throw new IllegalArgumentException("Non-positive period.");
sched(task, System.currentTimeMillis()+delay, period);
}
都看到了吧,最终的落脚点在sche这个方法上,这个和lock比较像哈,看一下sched本尊:简单来说 将task放入队列queue
private void sched(TimerTask task,long time,long period){
if(time<0)
throw new IllegalStateException("Timer already cancelled");
synchronized (queue){
if(!thread.newTasksMayBeScheduled)
throw new IllegalStateException("Timer already cancelled");
synchronized (task.lock){
if(task.state != TimerTask.VIRGIN)
throw new IllegalStateException("Task already scheduled cancelled");
//下一次执行时间
task.nextExecutionTime = time;
//时间片
task.period = period;
//状态
task.state = TimerTask.SCHDULED;
}
//放入队列
queue.add(task);
if(queue.getMin()==task){
//做一次notify操作
queue.notify();
}
}
}
queue属性的结果TaskQueue:
class TaskQueue {
//内部task个数不超过128 不会扩容
private TimerTask[] queue = new TimerTask[128];
private int size = 0;
//内部提供了
/*
1、add(TimerTask)增加一个任务、
2、size()任务队列的长度、
3、getMin()获取当前排序后最近需要执行的任务,下标为1,队列头部0不做任何操作、
4、get(int)获取指定下标的数据,包括下标0、
5、removeMin()删除当前最近执行的任务,即第一个元素,通常只调度一次的任务,执行完调用此方法,可以将TimerTask从队列中移除、
6、quickRemove(int)删除指定元素,只有在Timer发送purge(清除)且当对应的
7、TimerTask调用cancel才会被调用这个方法:即取消每个TimerTask后从队列中移除(任务在执行仍执行不过队列中被移除)这个cancel是TimerTask的,这个方法完成后将队列最后一个元素补充到这个位置,可能造成顺序不一致问题:后面方法会回补、
8、rescheduleMin(long newTime)重新设置当前任务的下一次执行时间,并在队列中将其重新排序到合适的位置,调用fixDown、
9、isEmpty()、clear()、
10、fixUp()当新增task时,先将元素放在队列的尾部 向前找是否有比自己还要晚执行的任务 人将两个任务的顺序进行交换、
11、fixDown()与Up相反,执行完一个任务后,加上一个时间片得到下一次的执行时间,将其顺序与后面任务进行对比、
12、heapify()队列的后半截,做一次fixeDown,回补quickRemove方法,大量qR顺序被打乱、将一半的区域做一次简单排序即可
*/
/**
* 找到一个合适的位置来交换
* 每次移动一个二进制位,找到合适的位置放下
* 顺序未必有序,只需要看到距离调度部分近的是有序性强的:一定顺序性
* @param k
*/
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++;
if (queue[k].nextExecutionTime <= queue[j].nextExecutionTime)
break;
TimerTask tmp = queue[j];
queue[j] = queue[k];
queue[k] = tmp;
k = j;
}
}
源码还有,继续拷贝,不过为了视觉,重来一个代码框吧,宝宝如此周到、心细,O(∩_∩)O哈哈~
//还是上面的类
public void cancel() {
synchronized(queue) {
thread.newTasksMayBeScheduled = false;
queue.clear();
queue.notify(); // In case queue was already empty.
}
}
//对*Task做cancle操作后,通purge回收这些cancel掉的类空间,顺序会乱
public int purge() {
int result = 0;
synchronized(queue) {
for (int i = queue.size(); i > 0; i--) {
if (queue.get(i).state == TimerTask.CANCELLED) {
queue.quickRemove(i);
result++;
}
}
if (result != 0)
queue.heapify();//重排序
}
return result;
}
调度、notify、情空队列:TimerThread:内部属性newTasksMayBeScheduled:
//通过queue通过构造方法传入,Timer传递给这个线程的
private TaskQueue queue;
public void run() {
try {
mainLoop();//主循环程序
} finally {
synchronized(queue) {
newTasksMayBeScheduled = false;
queue.clear(); // Eliminate obsolete references
}
}
}
几乎要把海子大神的博文给搬过来了,看来这篇躲避不了被标为“转载”的命运了,本来是奔着“原创”抄、不是、写的,原创啊原创,下次、诶~再说何时见吧
private void mainLoop() {
//死循环,除非遇到不能捕获的异常或break才会跳出
while (true) {
try {
TimerTask task;
boolean taskFired;
synchronized (queue) {
//跳出循环的条件:队列不为空,newTasksMayBeScheduled为false
while (queue.isEmpty() && newTaskMayBeScheduled)
queue.wait;//等待其他地方对queue发生notify操作
if (queue.isEmpty())
//newTasksMayBeScheduled=false则跳出
// 就是调用cancle,queue空、直接跳出外部死循环
break;//如果下面的任务还在跑 cancle不起作用
long currentTime, executionTime;
task = queue.getMin();
synchronized (task.lock) {
if (task.state == TimerTask.CANCELLED) {
queue.removeMin();
continue;// No action required, poll queue again
}
currentTime = System.currentTimeMillis();
//上次预计的执行时间
executionTime = task.nextExecutionTime;
//计算右边的值给taskFired,这个拆开更好看一些
if (taskFired = (executionTime <= currentTime)) {
if (task.period == 0) {//时间片
//no repeating ,remove
queue.removeMin();
task.state = TimerTask.EXECUTED;
} else {
//repeatring task,reschedule 重新设置最新时间并排序
queue.rescheduleMin(
//period负,按照当前系统时间+时间片计算下一次时间
//schedule和scheduleAtFixedRate的区别,内部通过正负数判定
task.period < 0 ? currentTime - task.period :
executionTime + task.period);
}
}
}
if (!taskFired)
//任务执行时间还未到,等待task hasn't yet fired ; wait
//可能会被其他线程操作add cancel 唤醒 因内部notify 时间不完全准确
queue.wait(executionTime - currentTime);
}
if(taskFired)
//线程需要,调用run:Task fired run it holding no locks
//TimerTask 不是多线程的run ,虽实现了Runnable 仅为了表示其实可执行的
task.run();
}catch (InterruptedException e){}
}
}
从上面的代码中可以发现,当发生add、cancel以及在threadReaper调用finalize方法的时候会被调用
发生add的时候(当队列还是空的时候)使得队列不为空就跳出循环
cancle设置了状态、否则不会进入这个循环
finalize()是Object中的方法,当垃圾回收器将要回收对象所占内存之前被调用,即当一个对象被虚拟机宣告死亡时会先调用它finalize()方法,让此对象处理它生前的最后事情(这个对象可以趁这个时机挣脱死亡的命运)
创建Timer的源码
//class TimerThread extends Thread {……}Timer内部包装了一个线程,用来做
//独立于外部线程的调度;TimerThread是个default类型的,默认情况下引用不
//到,被Timer自己使用
private TimerThread thread = new TimerThread(queue);
//队列:要调度的任务
private TaskQueue queue = new TaskQueue();
/*属性threadReaper,Object类型,只是重写了finalize,为了垃圾回收的时候,将相应的信息回收掉,做GC的回补:当timer由某种原因死掉而未被cancel时,清空队里中信息*/
// 通过Tiemer为前缀构造一个线程名称,不是主线程,主线程结束后timer自动结束
public Timer{
this("Time="+serialNumber());
}
//传入是否为守护进程,守护进程当且仅当进程结束时,自动注销掉
public Timer(boolean isDaemon){
this("Timer-"+serialNumber(),isDaemon);
}
public Timer(String name, boolean isDaemon) {
thread.setName(name);
thread.setDaemon(isDaemon);
thread.start();
}
public Timer(String name){
thread.setName(name);
/*创建新线程,不是守护线程,一直运行*/
thread.start();
}
将新创建的Timer改成守护线程:
TimerTask:队列方式一个一个顺序执行
timerTask只是实现了run方法的一个类,具体的TimerTask处理逻辑需要自己来实现
//20s后执行,每秒执行一次,可以实现多个TimerTask
Timer timer = new Timer();
timer.schedule(new TimerTask() {
public void run() {
System.out.println("abc");
}
}, 200000 , 1000);
注意:
1、计划时间早于当前时间,则理解执行任务
Timer和TimerTask的简单组合不是多线程:
一个Timer内部包装了一个Thread和一个Task队列,这个队列按照一定的方式将任务排队处理,包含的线程在Timer的构造方法调用时被启动,这个Thread的run无限循环这个task队列,若队列为空且没有发生cancel、将一直等待,如果等待完成、队列还是空的,则认为cancel了跳出死循环,结束任务,循环中如果发现任务需要执行的时间小于系统时间,则需要执行:根据任务的时间片重新计算下次执行时间,若时间片为0代表只执行一次,直接移除队列即可
但可以实现多线程,任何东西是否是多线程完全看个人意愿,多个Timer自然是多线程,每个Timer有自己的处理逻辑,当然Timer从这里来看并不是很适合很多任务短时间内的快速调度;在多线程领域我们更多用的是多线程的Executors.newScheduledThreadPool,完成对调度队列中线程池的处理,内部通过
new ScheduledThreadPoolExecutor来创建线程池的Executor的创建,当然也可以调用:
1 |
|
方法来创建一个DelegatedScheduledExecutorService其实这个类就是包装了下下scheduleExecutor,也就是这只是一个壳,英文理解就是被委派的意思,被托管的意思。
终于完了,但是有些地方还不是很理解,明天继续吧,感谢博主的分享,还有多线程核心编码作者;
哦、原来也是转载的,不过谢谢分享,文章主体的原文链接: http://blog.csdn.net/xieyuooo/article/details/8607220