java timer定时任务_JAVA定时任务Timer

故事起因

因业务需要,写了一个定时任务Timer,任务将在每天的凌晨2点执行,代码顺利码完,一切就绪,开始测试。运行程序,为了节省时间,将系统时间调整为第二天凌晨1点59分,看着秒针滴答滴答的转动,期盼着到2点时程序能正确运行,正暗暗欣喜之时,时间滑过2点,但是程序没有任何反应,啊哦,难道是我程序写错了。悲剧。

二次测试

首先检查自己写的程序没有什么问题。再次测试,先将时间调整为1点59分,打上断点,再运行程序,2点到来,程序运行到断点处,一步一步往下走,一切正常。为何,刚刚不是还是不能运行吗。又重复测试了几次,发现一个规律,先调整好时间后再运行程序一切正常,但是先运行程序再调整时间就什么没有任何反应。没办法了,只能研究一下JDK的Timer源码,看看内部有什么玄机。

了解内幕

我们先看看类的关系,见下图:

0a2bc34b49cd6ae74d21f9be5e7ff846.png

图1

其中Task是我自己写的任务类,这个类需要继承TimerTask,并且实现run()抽象方法,需要将任务执行的相关代码写在run方法中。

1 public class Task extendsTimerTask {2 @Override3 public voidrun() {4 System.out.println("do task...");5 }6 }

执行任务的方法如下:

1 public classTimerManager {2 publicTimerManager() {3 ……4

5 Timer timer = newTimer();6 Task task = newTask();7 //安排指定的任务在指定的时间开始进行重复的固定延迟执行。

8 timer.schedule(task, date, ConfigData.getDeltaTime() * 1000);9 }10 }

在执行任务的时候,我们只跟Timer打交道,所以先来了解一下Timer.

Timer的构造函数如下,又调用了自己的另一个构造函数 :

1 publicTimer() {2 this("Timer-" +serialNumber());3 }4

5 publicTimer(String name) {6 thread.setName(name);7 thread.start();8 }

到了这一步,我们需要了解thread是个什么玩意儿,我们来看看他的定义:

1 private TimerThread thread = new TimerThread(queue);

此时我们需要了解的对象成了TimerThread了,顺藤摸瓜,接着往下看吧:

1 class TimerThread extendsThread {2 boolean newTasksMayBeScheduled = true;3

4 privateTaskQueue queue;5

6 TimerThread(TaskQueue queue) {7 this.queue =queue;8 }9

10 public voidrun() {11 try{12 mainLoop();13 } finally{14 //Someone killed this Thread, behave as if Timer cancelled

15 synchronized(queue) {16 newTasksMayBeScheduled = false;17 queue.clear(); //Eliminate obsolete references

18 }19 }20 }21

22 /**

23 * The main timer loop. (See class comment.)24 */

25 private voidmainLoop() {26 while (true) {27 try{28 TimerTask task;29 booleantaskFired;30 synchronized(queue) {31 //Wait for queue to become non-empty

32 while (queue.isEmpty() &&newTasksMayBeScheduled)33 queue.wait();34 if(queue.isEmpty())35 break; //Queue is empty and will forever remain; die36

37 //Queue nonempty; look at first evt and do the right thing

38 longcurrentTime, executionTime;39 task =queue.getMin();40 synchronized(task.lock) {41 if (task.state ==TimerTask.CANCELLED) {42 queue.removeMin();43 continue; //No action required, poll queue again

44 }45 currentTime =System.currentTimeMillis();46 executionTime =task.nextExecutionTime;47 if (taskFired = (executionTime<=currentTime)) {48 if (task.period == 0) { //Non-repeating, remove

49 queue.removeMin();50 task.state =TimerTask.EXECUTED;51 } else { //Repeating task, reschedule

52 queue.rescheduleMin(53 task.period<0 ? currentTime -task.period54 : executionTime +task.period);55 }56 }57 }58 if (!taskFired) //Task hasn't yet fired; wait

59 queue.wait(executionTime -currentTime);60 }61 if (taskFired) //Task fired; run it, holding no locks

62 task.run();63 } catch(InterruptedException e) {64 }65 }66 }67 }

我们可以看到TimerThread主要就是mianLoop()方法,在mainLoop方法中有这么两行代码:

1 while (queue.isEmpty() &&newTasksMayBeScheduled)2 queue.wait();

当我们 new Timer(),然后两次调用Timer()的构造函数,并调用thread.start()时,就到了mainLoop方法的这两行代码了,此时的queue.isEmpty()为true,所以线程就wait在这个地方了。什么时候将他notify呢?我们接着往下看我们自己的代码,new Timer(),new Task()之后就到了下面的这行代码了:

1 timer.schedule(task, date, ConfigData.getDeltaTime() * 1000);

继续摸索一下timer.schedule()是怎么工作的:

1 public void schedule(TimerTask task, Date firstTime, longperiod) {2 if (period <= 0)3 throw new IllegalArgumentException("Non-positive period.");4 sched(task, firstTime.getTime(), -period);5 }6

7 private void sched(TimerTask task, long time, longperiod) {8 if (time < 0)9 throw new IllegalArgumentException("Illegal execution time.");10

11 synchronized(queue) {12 if (!thread.newTasksMayBeScheduled)13 throw new IllegalStateException("Timer already cancelled.");14

15 synchronized(task.lock) {16 if (task.state !=TimerTask.VIRGIN)17 throw newIllegalStateException(18 "Task already scheduled or cancelled");19 task.nextExecutionTime =time;20 task.period =period;21 task.state =TimerTask.SCHEDULED;22 }23

24 queue.add(task);25 if (queue.getMin() ==task)26 queue.notify();27 }28 }

当我们跟踪到方法sched时可以看到,这方法中设置了任务的下一次执行时间为传入的时间task.nextExecutionTime = time,然后把添加任务到队列中并notify队列。

到了这里我们要回到之前的wait位置了:

1 while (queue.isEmpty() &&newTasksMayBeScheduled)2 queue.wait();

现在queue.isEmpty()为false了,得继续往下运行,

1 currentTime =System.currentTimeMillis();2 executionTime =task.nextExecutionTime;3 if (taskFired = (executionTime<=currentTime))

程序取得当前的时间和任务下一次执行的时间,比较如果执行的时间还未到则任务执行为false,即taskFired=false。

1 if (!taskFired) //Task hasn't yet fired; wait

2 queue.wait(executionTime - currentTime);

所以程序为进入wait状态,要wait多久呢?时间为executionTime – currentTime

而如果当前执行程序的时间在任务执行的时间之后了,则任务执行为true,即taskFired=true。

1 if (taskFired) //Task fired; run it, holding no locks

2 task.run();

任务立即会被执行。

豁然开朗

到这里我们就清楚了为什么之前的测试是那样一个现象,当我们先运行程序再将时间调整为1点59分后,程序一直处于queue.wait(executionTime - currentTime)状态,需要wait的时间为executionTime – currentTime,所以刚过2点时程序是不会马上运行的,必须要等待 executionTime – currentTime的时间后才能执行任务。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值