Timer是用于管理在后台执行的延迟任务或周期性任务,其中的任务使用java.util.TimerTask表示。任务的执行方式有两种:
具体差别会在后面详细说明。
我们要实现一个定时任务,只需要实现TimerTask的run方法即可。每一个任务都有下一次执行时间nextExecutionTime(毫秒),如果是周期性的任务,那么每次执行都会更新这个时间为下一次的执行时间,当nextExecutionTime小于当前时间时,都会执行它。
Timer的具体使用方法非常简单,比如:
Timer timer = new Timer(); timer.schedule(new TimerTask() { @Override public void run() { System.out.println("Timer is running"); } }, 2000);上面这个定时任务表示在2秒后开始执行,只执行一次。当然还可以执行周期性任务,只需要添加schedule的第三个参数period,如:
Timer timer = new Timer(); timer. scheduleAtFixedRate(new TimerTask() { @Override public void run() { System.out.println("Timer is running"); } }, 2000, 5000);表示2秒后开始执行,然后每隔5秒执行一次。
对于每一个Timer,后台只使用一个线程来执行所有的任务。而所有的任务都保存到一个任务队列java.util.TaskQueue中,它是Timer的一个内部类,这是一个优先级队列,使用的算法是最小二叉堆(binary min heap)。
Timer的文档中说:
After the last live reference to a Timer object goes away and all outstanding tasks have completed execution, the timer‘s task execution thread terminates gracefully (and becomes subject to garbage collection).
意思是(没有完全按照原文翻译):当任务队列中所有的任务都执行完后,即没有一次性执行的任务,也没有周期性的任务,那么Timer的后台线程将优雅地终止,并成为垃圾回收的对象。
但是在测试时却发现,在执行完一个一次性任务后,任务线程却不终止,而是一直阻塞,这不知道是何原因,使用的JDK是1.7.0_79。
使用jstack查看到这个线程的状态如下:
"Timer-0" prio=5 tid=0x00007ff184046000 nid=0x5807 in Object.wait() [0x000000011ebea000] java.lang.Thread.State: WAITING (on object monitor) at java.lang.Object.wait(Native Method) - waiting on <0x00000007ab1bbea0> (a java.util.TaskQueue) at java.lang.Object.wait(Object.java:503) at java.util.TimerThread.mainLoop(Timer.java:526) - locked <0x00000007ab1bbea0> (a java.util.TaskQueue) at java.util.TimerThread.run(Timer.java:505)
可以看到Timer任务线程处理阻塞状态。
一个TimerTask有四种状态:
下面看看当调用Timer.schedule时发生了什么,所有调用Timer.schedule都是调用的一个私有方法sched。
比如延迟执行的任务(非周期性任务):
public void schedule(TimerTask task, long delay) { // 延迟不能小于0 if (delay < 0) throw new IllegalArgumentException("Negative delay.”); // 调用私有的sched方法,注意这里的执行时间是绝对时间,而period为0表示不是周期性任务 sched(task, System.currentTimeMillis()+delay, 0); } private void sched(TimerTask task, long time, long period) { if (time < 0) throw new IllegalArgumentException("Illegal execution time."); // Constrain value of period sufficiently to prevent numeric // overflow while still being effectively infinitely large. if (Math.abs(period) > (Long.MAX_VALUE >> 1)) period >>= 1; synchronized(queue) { // 如果定时器已经取消,那么不能再执行任何任务,所以抛出异常 if (!thread.newTasksMayBeScheduled) throw new IllegalStateException("Timer already cancelled."); synchronized(task.lock) { // 如果要执行的任务状态不是VIRGIN,那么抛出异常 if (task.state != TimerTask.VIRGIN) throw new IllegalStateException( "Task already scheduled or cancelled”); // 设置任务的执行时间,注意这个时间是绝对时间 task.nextExecutionTime = time; // 设置任务执行的周期 task.period = period; // 设置任务的状态为SCHEDULED task.state = TimerTask.SCHEDULED; } // 把任务添加到任务队列,等任务执行线程调试执行 queue.add(task); // 检查下一个将要执行的任务是不是当前添加的任务,如果是则通知后台任务线程 if (queue.getMin() == task) queue.notify(); } }执行任务的后台线程,Timer使用了一个内部类TimerThread,它有一个newTasksMayBeScheduled属性表示是否还能执行任务,默认为true,当调用cancel方法时设置为false,之后将不能再添加或执行任务。同时它还持有一个任务队列的引用,而不是直接引用Timer的任务队列,这是因为为了防止循环引用导致Timer无法被垃圾回收,以及任务线程不能正常终止。
下面看一下这个任务线程的实现:
public void run() { try { mainLoop(); } finally { // Someone killed this Thread, behave as if Timer cancelled synchronized(queue) { newTasksMayBeScheduled = false; queue.clear(); // Eliminate obsolete references } } }
实际上是调用的mainLoop方法:
private void mainLoop() { while (true) { try { TimerTask task; boolean taskFired; synchronized(queue) { // 等待任务队列变成非空,如果任务队列为空并且Timer未取消,就阻塞线程,等待任务队列为非空,从前面可以看到,每次添加完一个任务后,会通知这个线程来检查执行相应的任务 while (queue.isEmpty() && newTasksMayBeScheduled) queue.wait(); if (queue.isEmpty()) break; // Queue is empty and will forever remain; die // Queue nonempty; look at first evt and do the right thing 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; // 如果当前任务的执行时间如果<=当前时间,则执行该任务,否则不执行,同时可以注意到,如果在添加任务时,执行时间是一个过去的时间,也会执行 if (taskFired = (executionTime<=currentTime)) { // 如果不是非周期性任务,那么从任务队列中移除该任务,并把该任务的状态设置为EXECUTED if (task.period == 0) { // Non-repeating, remove queue.removeMin(); task.state = TimerTask.EXECUTED; } else { // Repeating task, reschedule // 如果是周期性任务,则重新设置任务的下一次执行时间,这里要注意period有可能为正和负 queue.rescheduleMin( task.period<0 ? currentTime - task.period : executionTime + task.period); } } } // 如果没有任务要执行,那么等待直到下一次任务执行的时间 if (!taskFired) // Task hasn't yet fired; wait queue.wait(executionTime - currentTime); } // 如何有任务需要执行,这里才真正执行任务的逻辑 if (taskFired) // Task fired; run it, holding no locks task.run(); } catch(InterruptedException e) { } } }其中重新设置任务的下一次执行时间的逻辑:
queue.rescheduleMin(task.period<0 ? currentTime - task.period : executionTime + task.period);
这里任务执行周期可为正和负:1、由于执行任务的线程只有一个,所以如果某个任务的执行时间过长,那么将破坏其他任务的定时精确性。如一个任务每1秒执行一次,而另一个任务执行一次需要5秒,那么如果是固定速率的任务,那么会在5秒这个任务执行完成后连续执行5次,而固定延迟的任务将丢失4次执行。
2、如果执行某个任务过程中抛出了异常,那么执行线程将会终止,导致Timer中的其他任务也不能再执行。
3、Timer使用的是绝对时间,即是某个时间点,所以它执行依赖系统的时间,如果系统时间修改了的话,将导致任务可能不会被执行。
由于Timer存在上面说的这些缺陷,在JDK1.5中,我们可以使用ScheduledThreadPoolExecutor来代替它,使用Executors.newScheduledThreadPool工厂方法或使用ScheduledThreadPoolExecutor的构造函数来创建定时任务,它是基于线程池的实现,不会存在Timer存在的上述问题,当线程数量为1时,它相当于Timer。
版权声明:本文为博主原创文章,未经博主允许不得转载。
原文地址:http://blog.csdn.net/mhmyqn/article/details/48070879