VB.net 2010 视频教程 VB.net 2010 视频教程 python基础视频教程
SQL Server 2008 视频教程 c#入门经典教程 Visual Basic从门到精通视频教程
当前位置:
首页 > 编程开发 > Java教程 >
  • Java 定时调配 Timer 类和定任务 TimerTask 类(一篇详细且完整的源码分析以及四种

前言

        在我们日常生活中,我们常常会遇到有关计时器的事情。如商城类项目会在某年某月某日某时某分某秒进行特价活动,那么当时间到达这个时间点上的时候该事件就会触发。

1、Timer 类构造函数摘要

复制代码
1 Timer()
2           创建一个新计时器。
3 Timer(boolean isDaemon)
4           创建一个新计时器,可以指定其相关的线程作为守护线程运行。
5 Timer(String name)
6           创建一个新计时器,其相关的线程具有指定的名称
7 Timer(String name, boolean isDaemon)
8           创建一个新计时器,其相关的线程具有指定的名称,并且可以指定作为守护线程运行
复制代码

2、Timer 类方法摘要

复制代码
 1 void cancel()   
 2           终止此计时器,丢弃所有当前已安排的任务。   
 3 int purge()   
 4           从此计时器的任务队列中移除所有已取消的任务。   
 5 void schedule(TimerTask task, Date time)   
 6           安排在指定的时间执行指定的任务。   
 7 void schedule(TimerTask task, Date firstTime, long period)   
 8           安排指定的任务在指定的时间开始进行重复的固定延迟执行。   
 9 void schedule(TimerTask task, long delay)   
10           安排在指定延迟后执行指定的任务。   
11 void schedule(TimerTask task, long delay, long period)   
12           安排指定的任务从指定的延迟后开始进行重复的固定延迟执行。   
13 void scheduleAtFixedRate(TimerTask task, Date firstTime, long period)   
14           安排指定的任务在指定的时间开始进行重复的固定速率执行。   
15 void scheduleAtFixedRate(TimerTask task, long delay, long period)   
16           安排指定的任务在指定的延迟后开始进行重复的固定速率执行。 
复制代码

3、TimerTask 类方法摘要

复制代码
1 boolean cancel()   
2           取消此计时器任务。   
3 void run()   
4           此计时器任务要执行的操作。   
5 long scheduledExecutionTime()   
6           返回此任务最近实际 执行的已安排 执行时间。
复制代码

4、Timer 源码分析

复制代码
  1 package java.util;
  2 import dalvik.annotation.optimization.ReachabilitySensitive;
  3 import java.util.Date;
  4 import java.util.concurrent.atomic.AtomicInteger;
  5 
  6 
  7 // 定时器类
  8 public class Timer {
  9 
 10     // 任务队列
 11     @ReachabilitySensitive
 12     private final TaskQueue queue = new TaskQueue();
 13 
 14     // 内置线程
 15     @ReachabilitySensitive
 16     private final TimerThread thread = new TimerThread(queue);
 17 
 18     /*
 19         只是重写了 finalize 方法而已,是为了垃圾回收的时候,将相应的信息回收掉,做 GC 的回补,
 20         也就是当 timer 线程由于某种原因死掉了,而未被 cancel,里面的队列中的信息需要清空掉,
 21         不过我们通常是不会考虑这个方法的,所以知道 java 写这个方法是干什么的就行了。
 22     */
 23     private final Object threadReaper = new Object() {
 24         protected void finalize() throws Throwable {
 25             synchronized(queue) {
 26                 thread.newTasksMayBeScheduled = false;
 27                 queue.notify();
 28             }
 29         }
 30     };
 31 
 32     private final static AtomicInteger nextSerialNumber = new AtomicInteger(0);
 33     private static int serialNumber() {
 34         return nextSerialNumber.getAndIncrement();
 35     }
 36 
 37     // 空的构造函数,这里会调用 Timer(String name)  给相应线程设置默认名称
 38     public Timer() {
 39         this("Timer-" + serialNumber());
 40     }
 41 
 42     // 是否设置为守护线程并设置默认名称
 43     public Timer(boolean isDaemon) {
 44         this("Timer-" + serialNumber(), isDaemon);
 45     }
 46 
 47     // 给相应线程设置指定名称并开启线程
 48     public Timer(String name) {
 49         thread.setName(name);
 50         thread.start();
 51     }
 52 
 53     // 设置指定名称以及是否设置为守护线程并开启线程
 54     public Timer(String name, boolean isDaemon) {
 55         thread.setName(name);
 56         thread.setDaemon(isDaemon);
 57         thread.start();
 58     }
 59 
 60     // 调配任务,设置任务多久后开始
 61     public void schedule(TimerTask task, long delay) {
 62         if (delay < 0)
 63             throw new IllegalArgumentException("Negative delay.");
 64         sched(task, System.currentTimeMillis()+delay, 0);
 65     }
 66 
 67     // 调配任务,设置任务什么时候开始
 68     public void schedule(TimerTask task, Date time) {
 69         sched(task, time.getTime(), 0);
 70     }
 71 
 72     // 调配任务,设置任务多久后开始循环执行并间隔多久
 73     public void schedule(TimerTask task, long delay, long period) {
 74         if (delay < 0)
 75             throw new IllegalArgumentException("Negative delay.");
 76         if (period <= 0)
 77             throw new IllegalArgumentException("Non-positive period.");
 78         sched(task, System.currentTimeMillis()+delay, -period);
 79     }
 80 
 81     // 调配任务,设置任务循环执行时间并间隔多久
 82     public void schedule(TimerTask task, Date firstTime, long period) {
 83         if (period <= 0)
 84             throw new IllegalArgumentException("Non-positive period.");
 85         sched(task, firstTime.getTime(), -period);
 86     }
 87 
 88     // 调配任务,设置任务多久后开始循环执行并间隔多久
 89     public void scheduleAtFixedRate(TimerTask task, long delay, long period) {
 90         if (delay < 0)
 91             throw new IllegalArgumentException("Negative delay.");
 92         if (period <= 0)
 93             throw new IllegalArgumentException("Non-positive period.");
 94         sched(task, System.currentTimeMillis()+delay, period);
 95     }
 96 
 97     // 调配任务,设置任务循环执行时间并间隔多久
 98     public void scheduleAtFixedRate(TimerTask task, Date firstTime, long period) {
 99         if (period <= 0)
100             throw new IllegalArgumentException("Non-positive period.");
101         sched(task, firstTime.getTime(), period);
102     }
103 
104     // 任务调配
105     private void sched(TimerTask task, long time, long period) {
106         if (time < 0)
107             throw new IllegalArgumentException("Illegal execution time.");
108 
109         if (Math.abs(period) > (Long.MAX_VALUE >> 1))
110             period >>= 1;
111 
112         synchronized(queue) {
113             if (!thread.newTasksMayBeScheduled)
114                 throw new IllegalStateException("Timer already cancelled.");
115 
116             synchronized(task.lock) {
117                 if (task.state != TimerTask.VIRGIN)
118                     throw new IllegalStateException("Task already scheduled or cancelled");
119                 task.nextExecutionTime = time;
120                 task.period = period;
121                 task.state = TimerTask.SCHEDULED;
122             }
123 
124             queue.add(task);
125             if (queue.getMin() == task)
126                 queue.notify();
127         }
128     }
129 
130     // 中止任务, 一旦执行了这个方法timer就会结束掉
131     public void cancel() {
132         synchronized(queue) {
133             thread.newTasksMayBeScheduled = false;
134             queue.clear();
135             queue.notify();
136         }
137     }
138 
139     // 清除 Timer 中标记为 CANCELLED 的 TimerTask, 返回清除个数
140     public int purge() {
141         int result = 0;
142 
143         synchronized(queue) {
144             for (int i = queue.size(); i > 0; i--) {
145                 if (queue.get(i).state == TimerTask.CANCELLED) {
146                     queue.quickRemove(i);
147                     result++;
148                 }
149             }
150 
151             if (result != 0)
152                 queue.heapify();
153         }
154 
155         return result;
156     }
157 }
158 
159 
160 // 自定义线程类
161 class TimerThread extends Thread {
162 
163     boolean newTasksMayBeScheduled = true;
164 
165     private TaskQueue queue;
166 
167     TimerThread(TaskQueue queue) {
168         this.queue = queue;
169     }
170 
171     public void run() {
172         try {
173             mainLoop();
174         } finally {
175             synchronized(queue) {
176                 newTasksMayBeScheduled = false;
177                 queue.clear();
178             }
179         }
180     }
181 
182     // 执行任务(重点代码块)
183     private void mainLoop() {
184         while (true) {
185             try {
186                 TimerTask task;
187                 boolean taskFired;
188                 synchronized(queue) {
189                     while (queue.isEmpty() && newTasksMayBeScheduled)
190                         queue.wait();
191                     if (queue.isEmpty())
192                         break;
193 
194                     long currentTime, executionTime;
195                     task = queue.getMin();
196                     synchronized(task.lock) {
197                         if (task.state == TimerTask.CANCELLED) {
198                             queue.removeMin();
199                             continue;
200                         }
201                         currentTime = System.currentTimeMillis();
202                         executionTime = task.nextExecutionTime;
203                         if (taskFired = (executionTime<=currentTime)) {
204                             if (task.period == 0) {        // 区分是否重复执行任务,间隔时间为 0 则执行一次
205                                 queue.removeMin();
206                                 task.state = TimerTask.EXECUTED;
207                             } else {
208                                 // 区分 schedule 和 scheduleAtFixedRate
209                                 queue.rescheduleMin(
210                                 task.period<0 ? currentTime   - task.period
211                                         : executionTime + task.period);
212                             }
213                         }
214                     }
215                     if (!taskFired)         // 下次执行时间大于当前时间 等待
216                         queue.wait(executionTime - currentTime);
217                 }
218                 if (taskFired)
219                     task.run();
220             } catch(InterruptedException e) {
221             }
222         }
223     }
224 }
225 
226 // 自定义任务管理类
227 class TaskQueue {
228 
229     // 初始化  128个空间,实际使用127个  位置编号为0的位置不使用
230     private TimerTask[] queue = new TimerTask[128];
231 
232     private int size = 0;
233 
234     // 任务队列长度
235     int size() {
236         return size;
237     }
238 
239     // 添加任务,如果空间不足,空间*2,,然后排序(将nextExecutionTime最小的排到1位置)
240     void add(TimerTask task) {
241         if (size + 1 == queue.length)
242             queue = Arrays.copyOf(queue, 2*queue.length);
243 
244         queue[++size] = task;
245         fixUp(size);
246     }
247 
248     // 得到最小的nextExecutionTime的任务
249     TimerTask getMin() {
250         return queue[1];
251     }
252 
253     // 得到指定位置的任务
254     TimerTask get(int i) {
255         return queue[i];
256     }
257 
258     // 删除最小nextExecutionTime的任务,排序(将nextExecutionTime最小的排到1位置)
259     void removeMin() {
260         queue[1] = queue[size];
261         queue[size--] = null;
262         fixDown(1);
263     }
264 
265     // 快速删除指定位置的任务
266     void quickRemove(int i) {
267         assert i <= size;
268 
269         queue[i] = queue[size];
270         queue[size--] = null;
271     }
272 
273     // 重新设置最小nextExecutionTime的任务的nextExecutionTime,排序(将nextExecutionTime最小的排到1位置)
274     void rescheduleMin(long newTime) {
275         queue[1].nextExecutionTime = newTime;
276         fixDown(1);
277     }
278 
279     // 数组是否为空
280     boolean isEmpty() {
281         return size==0;
282     }
283 
284     // 清空数组
285     void clear() {
286         for (int i=1; i<=size; i++)
287             queue[i] = null;
288 
289         size = 0;
290     }
291 
292     // 将nextExecutionTime最小的排到1位置
293     private void fixUp(int k) {
294         while (k > 1) {
295             int j = k >> 1;
296             if (queue[j].nextExecutionTime <= queue[k].nextExecutionTime)
297                 break;
298             TimerTask tmp = queue[j];  queue[j] = queue[k]; queue[k] = tmp;
299             k = j;
300         }
301     }
302 
303     // 将nextExecutionTime最小的排到1位置
304     private void fixDown(int k) {
305         int j;
306         while ((j = k << 1) <= size && j > 0) {
307             if (j < size && queue[j].nextExecutionTime > queue[j+1].nextExecutionTime)
308                 j++; // j indexes smallest kid
309             if (queue[k].nextExecutionTime <= queue[j].nextExecutionTime)
310                 break;
311             TimerTask tmp = queue[j];  queue[j] = queue[k]; queue[k] = tmp;
312             k = j;
313         }
314     }
315 
316     // 排序(将nextExecutionTime最小的排到1位置)   在快速删除任务后调用
317     void heapify() {
318         for (int i = size/2; i >= 1; i--)
319             fixDown(i);
320     }
321 }
复制代码

5、TimerTask 源码

复制代码
 1 package java.util;
 2 
 3 
 4 public abstract class TimerTask implements Runnable {
 5 
 6     final Object lock = new Object();
 7 
 8     int state = VIRGIN;         // 状态 ,未使用,正在使用,非循环,使用完毕
 9 
10     static final int VIRGIN = 0;            // 未使用
11 
12     static final int SCHEDULED   = 1;       // 正在循环
13 
14     static final int EXECUTED    = 2;       // 非循环
15 
16     static final int CANCELLED   = 3;       // 使用完毕
17 
18     long nextExecutionTime;                 // 任务执行时间
19 
20     long period = 0;                        // 任务循环执行间隔时间
21 
22     protected TimerTask() {
23     }
24 
25     // 自定义任务
26     public abstract void run();
27 
28     // 退出   任务执行完毕后,退出返回 true ,未执行完 就退出 返回false
29     public boolean cancel() {
30         synchronized(lock) {
31             boolean result = (state == SCHEDULED);
32             state = CANCELLED;
33             return result;
34         }
35     }
36 
37     // 返回 时间
38     public long scheduledExecutionTime() {
39         synchronized(lock) {
40             return (period < 0 ? nextExecutionTime + period
41                     : nextExecutionTime - period);
42         }
43     }
44 }
复制代码

6、Timer 与 TimerTask

        Timer:Timer 是一个线程设施,可以用来实现某一个时间或某一段时间后安排某一个任务执行一次或定期重复执行。该功能需要和 TimerTask 类配合使用。每个 Timer 对象对应的是一个线程,因此计时器所执行的任务应该迅速完成,否则会延迟后续的任务执行。

        TimerTask:TimerTask 用于实现Timer 类安排的一次或重复执行某个任务,而具体的任务内容在 TimerTask 的 run 方法中去实现。

7、Timer 定时器的使用方法

复制代码
 1 package cn.pda.serialport;
 2 
 3 import java.util.Calendar;
 4 import java.util.Date;
 5 import java.util.Timer;
 6 import java.util.TimerTask;
 7 
 8 public class TimerTest {
 9 
10     public static void main(String[] args) {
11         // 声明下,这里单位为毫秒,所以 1000 毫秒为 1 秒
12         timer1();
13 //        timer2();
14 //        timer3();
15 //        timer4();
16     }
17 
18     // 第一种方法:实现 TimerTask 任务,指定在两秒后执行
19     public static void timer1() {
20         Timer timer = new Timer();
21         timer.schedule(new TimerTask() {
22             @Override
23             public void run() {
24                 System.out.println("***** 自定义任务 *****");
25             }
26         }, 2000);
27     }
28 
29     // 第二种方法:实现 TimerTask 任务,指定在两秒后循环执行,每次执行完后间隔五秒
30     public static void timer2() {
31         Timer timer = new Timer();
32         timer.schedule(new TimerTask() {
33             @Override
34             public void run() {
35                 System.out.println("***** 自定义任务 *****");
36             }
37         }, 2000, 5000);
38     }
39 
40     // 第三种方法:实现 TimerTask 任务,指定在两秒后循环执行,每次执行完后间隔五秒
41     public static void timer3() {
42         Timer timer = new Timer();
43         timer.scheduleAtFixedRate(new TimerTask() {
44             @Override
45             public void run() {
46                 System.out.println("***** 自定义任务 *****");
47             }
48         }, 2000, 5000);
49     }
50 
51     // 第四种方法:实现 TimerTask 任务,指定在某个时间点循环执行,每次执行完间隔二十四小时
52     public static void timer4() {
53         Calendar calendar = Calendar.getInstance();
54         calendar.set(Calendar.HOUR_OF_DAY, 12);     // 控制时
55         calendar.set(Calendar.MINUTE, 0);           // 控制分
56         calendar.set(Calendar.SECOND, 0);           // 控制秒
57         Date time = calendar.getTime();             // 得出执行任务的时间,此处为今天的12:00:00
58         Timer timer = new Timer();
59         timer.scheduleAtFixedRate(new TimerTask() {
60             public void run() {
61                 System.out.println("***** 自定义任务 *****");
62             }
63         }, time, 1000 * 60 * 60 * 24);// 这里设定将延时每天固定执行
64     }
65 
66 }
复制代码

        schedule 循环方式的用法以 scheduleAtFixedRate 一致,严格来说使用的方式远不止这节,这里只不过是简略的举例。若是深入扩展还可以使用自定义类继承 Timer 或 TimerTask 类来重写里面的方法,或者扩展新的功能,不过应该很少会用到。

8、schedule 和 scheduleAtFixedRate  区别

        schedule 方法和 scheduleAtFixedRate 方法都可以实现任务的延时和不延时执行且都会按顺序执行,它们之间的区别体现有两点;第一:任务循环执行时,下次任务的执行时间;第二:当循环任务出现卡顿时,它们对下次任务执行时间的处理就很明显的提现了它们之间的区别。具体的你们也可以细看 Timer 类的内置线程类 TimerThead 的 mainLoop 方法,这个方法算是挺核心的部分,尽量去读懂这个方法。为了方便查看,我再单独贴出 mainLoop 方法的代码。

复制代码
 1 private void mainLoop() {
 2     while (true) {
 3         try {
 4             TimerTask task;
 5             boolean taskFired;
 6             synchronized(queue) {
 7                 while (queue.isEmpty() && newTasksMayBeScheduled)
 8                     queue.wait();
 9                 if (queue.isEmpty())
10                     break;
11 
12                 long currentTime, executionTime;
13                 task = queue.getMin();
14                 synchronized(task.lock) {
15                     if (task.state == TimerTask.CANCELLED) {
16                         queue.removeMin();
17                         continue;
18                     }
19                     currentTime = System.currentTimeMillis();
20                     executionTime = task.nextExecutionTime;
21                     if (taskFired = (executionTime<=currentTime)) {
22                         if (task.period == 0) {
23                             queue.removeMin();
24                             task.state = TimerTask.EXECUTED;
25                         } else {
26                             queue.rescheduleMin(
27                                     task.period<0 ? currentTime   - task.period
28                                             : executionTime + task.period);
29                         }
30                     }
31                 }
32                 if (!taskFired)
33                     queue.wait(executionTime - currentTime);
34             }
35             if (taskFired)
36                 task.run();
37         } catch(InterruptedException e) {
38         }
39     }
40 }
复制代码

        schedule:每次执行完后重新根据(当前时间 + 间隔时间)决定下次循环任务的执行时间

        scheduleAtFixedRate:每次执行完后重新根据(上次任务执行时间 + 间隔时间)决定下次循环任务执行时间

        举个例子:我想制定一个任务,在 5 秒后执行,然后每次执行后都要间隔 5 秒。正常情况下两种方法不会有什么偏差,且会按照设定好的 5、10、15、20 ...... 如此规律的时间中循环下去。但在执行到第 10 秒时,程序所运行的电脑或服务器卡了 3 秒,然后就会出现以下处理情况。

        schedule:卡了 3 秒后当前时间段就在 13 秒,所以下次任务就是 13 秒 + 5 秒间隔在 18 秒时间段执行,以此类推就是 18、23、28、33、38.

        scheduleAtFixedRate:而 scheduleAtFixedRate 则是按照上次任务时间 + 间隔时间,所以下次任务就是 10 秒 + 5 秒间隔在 15 秒时间段执行,其实真正只间隔了 2 秒。在这里就不会有什么印象,但是试想下,如果卡的时间更久了呢,要是卡了 6、8、10、16 秒的时间呢。是的,在卡了 10 秒后,当前时间就是 20 秒,而 scheduleAtFixedRate 对下次任务的执行时间则还是 15 秒。这是就出现了 scheduleAtFixedRate 快速执行两次追赶当前时间的情况。

        总结:scheduleAtFixedRate 在按照规定好的时间段执行,如果出现了意外卡段,会立即多次执行把漏掉的几个时间段任务补上,然后追赶上当前时间下个任务时间段。而 schedule 则是直接改变了原来时间段的安排,重新以卡顿后的时间作为起始时间,重新安排接下来的执行时间段。

        建议:如果只是想做个简单的倒计时判断大可不用这么麻烦,毕竟东西虽好,但未必最适合,为了方便贴出代码如下。

1 long deferDateTime = System.currentTimeMillis() + 2000    // 2000 为延迟时间,延迟两秒
2 if (deferDateTime <= System.currentTimeMillis()) {
3         // 在延迟时间小于或等于当前时间时去做某些事情
4 }

 

来源:https://www.cnblogs.com/chenxiaozi/p/14204836.html


相关教程