-
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