如果你使用Java语言进行开发,对于定时执行任务这样的需求,自然而然会想到使用Timer和TimerTask完成任务,我最近就使用 Timer和TimerTask完成了一个定时执行的任务,实现得没有问题,但当在TimerTaks的run()方法中使用 Thread.sleep()方式时,可能会出现奇怪的现象,好像Timer失效了,网上查了一下,倒是有人遇到了相同的问题,但是并没有找到一篇解释为什么会出现这种情况,期待有某位达人能够分析清楚这个问题。
遇到了这样的问题,始终让我不爽,于是看了一下Timer的源码,先将了解到的内容整理如下,接下来再看看Thread.sleep()的源码,看能否找到问题所在。
在Java中,与定时任务执行相关的类很少,只有Timer、TimerTask、TimerThread、TaskQueue几个,其中每个类的职责大致如下:
Timer:一个Task的调度类,和TimerTask一样,暴露给最终用户使用的类,通过schedule方法安排Task的执行计划。该类通过TaskQueue和TimerThread类完成Task的调度。
TimerTask:实现Runnable接口,表明每一个任务均为一个独立的线程。通过run()方法提供用户定制自己任务。该类有一个比较重要的成员变量nextExecutionTime ,表示下一次执行该任务的时间。以后会看到,Timer机制就是靠这个值安排Task执行的。
TimerThread:继承于Thread,是真正执行Task的类。
TaskQueue:一个存储Task的数据结构,内部由一个最小堆实现,堆的每个成员为一个TimeTask,每个Task依靠其 nextExecutionTime值进行排序,也就是说,nextExecutionTime最小的任务在队列的最前端,从而能够现实最早执行。
要想使用Timer,用户只需要了解Timer和TimerTask,下面现已一个最基本的Timer和TimerTask使用案例入手,来看一下Timer内部的实现原理。
01
|
import java.util.Timer;
|
02
|
03
|
import java.util.TimerTask;
|
04
|
05
|
import org.junit.Test;
|
06
|
07
|
|
08
|
09
|
class TestTimerTask extends TimerTask
{
|
10
|
11
|
@Override
|
12
|
13
|
public void run()
{
|
14
|
15
|
System.out.println("TestTimerTask
is running......");
|
16
|
17
|
}
|
18
|
19
|
}
|
20
|
21
|
public class TimerTaskTest
{
|
22
|
23
|
@Test
|
24
|
25
|
public void testTimerTask()
{
|
26
|
27
|
Timer
timer = new Timer();
|
28
|
29
|
timer.schedule(new TestTimerTask(), 0, 10);
|
30
|
31
|
}
|
32
|
33
|
}
|
上面的代码是一个典型的Timer&TimerTask的应用,下面先来看一下new Timer()干了什么事,其源码如下:
public Timer(String name) {
thread.setName(name); //thread为TimerThread实例。
thread.start();
}
从上面的源代码可以知道,创建Timer对象的同时也启动了TimerThread线程。下面来看看TimerThread干了什么事:
01
|
public void run()
{
|
02
|
03
|
try {
|
04
|
05
|
mainLoop(); //线程真正执行的代码在这个私有方法中
|
06
|
07
|
} finally {
|
08
|
09
|
//
Someone killed this Thread, behave as if Timer cancelled
|
10
|
11
|
synchronized(queue)
{
|
12
|
13
|
newTasksMayBeScheduled
= false;
|
14
|
15
|
queue.clear(); //
Eliminate obsolete references
|
16
|
17
|
}
|
18
|
19
|
}
|
20
|
21
|
}
|
接着来看看私有方法mainLoop()干了什么事:
01
|
private void mainLoop()
{
|
02
|
03
|
while (true)
{
|
04
|
05
|
try {
|
06
|
07
|
TimerTask
task;
|
08
|
09
|
booleantaskFired; //是否已经到达Task的执行时间,如果已经到达,设置为true,否则置为false
|
10
|
11
|
synchronized(queue)
{
|
12
|
13
|
//
Wait for queue to become non-empty
|
14
|
15
|
while (queue.isEmpty()
&& newTasksMayBeScheduled)
|
16
|
17
|
queue.wait(); //由此可以看出,Timer通过wait
& notify 方法安排线程之间的同步
|
18
|
19
|
if (queue.isEmpty())
|
20
|
21
|
break; //
Queue is empty and will forever remain; die
|
22
|
23
|
|
24
|
25
|
//
Queue nonempty; look at first evt and do the right thing
|
26
|
27
|
long currentTime,
executionTime;
|
28
|
29
|
task
= queue.getMin();
|
30
|
31
|
synchronized(task.lock)
{
|
32
|
33
|
if (task.state
== TimerTask.CANCELLED) {
|
34
|
35
|
queue.removeMin();
|
36
|
37
|
continue; //
No action required, poll queue again
|
38
|
39
|
}
|
40
|
41
|
currentTime
= System.currentTimeMillis();
|
42
|
43
|
executionTime
= task.nextExecutionTime;
|
44
|
45
|
if(taskFired
= (executionTime<=currentTime)) { //Task的执行时间已到,设置taskFired为true
|
46
|
47
|
if (task.period
== 0)
{ //
Non-repeating, remove
|
48
|
49
|
queue.removeMin(); //移除队列中的当前任务
|
50
|
51
|
task.state
= TimerTask.EXECUTED;
|
52
|
53
|
} else { //
Repeating task, reschedule
|
54
|
55
|
queue.rescheduleMin( //重新设置任务的下一次执行时间
|
56
|
57
|
task.period<0 ?
currentTime - task.period
|
58
|
59
|
:
executionTime + task.period);
|
60
|
61
|
}
|
62
|
63
|
}
|
64
|
65
|
}
|
66
|
67
|
if (!taskFired) //
Task hasn't yet fired; wait
|
68
|
69
|
queue.wait(executionTime
- currentTime); //还没有执行时间,通过wait等待特定时间
|
70
|
71
|
}
|
72
|
73
|
if (taskFired) //
Task fired; run it, holding no locks
|
74
|
75
|
task.run(); //已经到达执行时间,执行任务
|
76
|
77
|
} catch(InterruptedException
e) {
|
78
|
79
|
}
|
80
|
81
|
}
|
82
|
83
|
}
|
也就是说,一旦创建了Timer类的实例,就一直存在一个循环在遍历queue中的任务,如果有任务的话,就通过thread去执行该任务,否则线程通过wait()方法阻塞自己,由于没有任务在队列中,就没有必要继续thread中的循环。
上面提到,如果Timer的任务队列中不包含任务时,Timer中的TimerThread线程并不会执行,接着来看看为Timer添加任务后会出现怎样的情况。为Timer添加任务就是timer.schedule()干的事,schedule()方法直接调用Timer的私有方法 sched(),sched()是真正安排Task的地方,其源代码如下:
01
|
private void sched(TimerTask
task, long time, long period)
{
|
02
|
03
|
if (time
< 0)
|
04
|
05
|
throw new IllegalArgumentException("Illegal
execution time.");
|
06
|
07
|
|
08
|
09
|
synchronized(queue)
{
|
10
|
11
|
if (!thread.newTasksMayBeScheduled)
|
12
|
13
|
throw new IllegalStateException("Timer
already cancelled.");
|
14
|
15
|
|
16
|
17
|
synchronized(task.lock)
{
|
18
|
19
|
if(task.state
!= TimerTask.VIRGIN) //我喜欢virgin状态,其他状态表明该Task已经被schedule过了
|
20
|
21
|
throw new IllegalStateException(
|
22
|
23
|
"Task
already scheduled or cancelled");
|
24
|
25
|
|
26
|
27
|
//设置Task下一次应该执行的时间,
由System.currentTimeMillis()+/-delay得到
|
28
|
29
|
task.nextExecutionTime
= time;
|
30
|
31
|
task.period
= period;
|
32
|
33
|
task.state
= TimerTask.SCHEDULED;
|
34
|
35
|
}
|
36
|
37
|
|
38
|
39
|
queue.add(task); //queue为TaskQueue类的实例,添加任务到队列中
|
40
|
41
|
if(queue.getMin()
== task) //获取队列中nextExecutionTime最小的任务,如果与当前任务相同
|
42
|
43
|
queue.notify(); //还记得前面看到的queue.wait()方法么
|
44
|
45
|
}
|
46
|
47
|
}
|
不要奇怪,为什么要判断queue.getMin() == task时,才通过queue.notify()恢复执行。因为这种方式已经满足所有的唤醒要求了。
如果安排当前Task之前queue为空,显然上述判断为true,于是mainLoop()方法能够继续执行。
如果安排当前Task之前queue不为空,那么mainLoop()方法不会一直被阻塞,不需要notify方法调用。
调用该方法还有一个好处是,如果当前安排的Task的下一次执行时间比queue中其余Task的下一次执行时间都要小,通过notify方法可以提前打开queue.wait(executionTime - currentTime)方法对mainLoop()照成的阻塞,从而使得当前任务能够被优先执行,有点抢占的味道。
上述分析可以看出,Java中Timer机制的实现仅仅使用了JDK中的方法,通过wait & notify机制实现,其源代码也非常简单,但可以想到的是这种实现机制会对开发者造成一种困扰,sched()方法中可以看出,对于一个重复执行的任务,Timer的实现机制是先安排Task下一次执行的时间,然后再启动Task的执行,如果Task的执行时间大于下一次执行的间隔时间,可能出现不可预期的错误。当然,了解了Timer的实现原理,修改这种实现方式也就非常简单了。