Java 内置定时器 定时器
最编程
2024-07-10 08:10:58
...
Timer是Java内置的一个定时任务,类似于JavaScript里面的setTimeout()和setInterval()方法,可以延迟一定的时间执行任务,也可以按时间间隔重复执行任务。
Timer实际上就是启动了一个线程进行任务处理的,是异步的。
import java.util.Date; import java.util.Timer; import java.util.TimerTask; public class Test { public static int times = 1; /** * Timer基本用法 * */ public static void main(String[] args) { Timer t = new Timer(); TimerTask task = new TimerTask() { @Override public void run() { System.err.println(new Date() + " ==> " + Thread.currentThread().getName() + "第" + Test.times + "次执行"); Test.times++; if(Test.times >= 5) { System.err.println("定时器结束"); cancel(); } } }; t.schedule(task , 1000, 2000); /** * scheduleAtFixedRate和schedule 的区别 * schedule 是按任务真实执行时间来计算下次执行时间的。 * scheduleAtFixedRate 是按上个任务的计划执行时间来计算下次执行时间的。 * 举个例子: * 任务的执行间隔是2s,首次执行延迟时长为1s,当前时间是15:00:00 * schedule: * 第一次执行时间为15:00:01,下次执行时间为15:00:03 * 第二次执行时线程抢到CPU是在15:00:04,那第二次执行是在15:00:04,下次执行时间是(15:00:04 + 2s,也就是15:00:06) * scheduleAtFixedRate: * 第一次执行时间为15:00:01,下次执行时间为15:00:03 * 第二次执行时线程抢到CPU是在15:00:04,那第二次执行是在15:00:04,下次执行时间是(15:00:03 + 2s,也就是15:00:05) * */ } }
Timer的相关方法
先上点源码让你们看看,好有个大概印象
public class Timer { /** * 任务队列 */ private final TaskQueue queue = new TaskQueue(); /** * 定时器线程 */ private final TimerThread thread = new TimerThread(queue); /** 线程序号id */ private final static AtomicInteger nextSerialNumber = new AtomicInteger(0); /** * 获取下一个线程序号 * */ private static int serialNumber() { return nextSerialNumber.getAndIncrement(); } /** * 构造函数,默认线程名是Timer-0 * */ public Timer() { this("Timer-" + serialNumber()); } /** * 当我们一创建Timer对象,线程就已经启动了 * */ public Timer(String name) { thread.setName(name); thread.start(); } /*** * 添加定时任务,以上一次执行任务的<真实时间>做为period的开始计算时间 * @param task 定时任务,实现了TimerTask的类的对象 * @param delay 延迟多少毫秒后再执行 * @param period 重复执行的时间间隔 * */ public void schedule(TimerTask task, long delay, long period) { if (delay < 0) throw new IllegalArgumentException("Negative delay."); if (period <= 0) throw new IllegalArgumentException("Non-positive period."); sched(task, System.currentTimeMillis()+delay, -period); } /*** * 添加定时任务,以上一次执行任务的<计划时间>做为period的开始计算时间 * @param task 定时任务,实现了TimerTask的类的对象 * @param delay 延迟多少毫秒后再执行 * @param period 重复执行的时间间隔 * */ public void scheduleAtFixedRate(TimerTask task, long delay, long period) { if (delay < 0) throw new IllegalArgumentException("Negative delay."); if (period <= 0) throw new IllegalArgumentException("Non-positive period."); sched(task, System.currentTimeMillis()+delay, period); } /** * * */ private void sched(TimerTask task, long time, long period) { if (time < 0) throw new IllegalArgumentException("Illegal execution time."); // 如果时间间隔很大,将其除2,防止溢出,因为System.currentTimeMillis()也只是返回一个long型数值,你 // 一个period就已经足够大了,再加上当前系统毫秒数,就可能超多long的最大值了 if (Math.abs(period) > (Long.MAX_VALUE >> 1)) period >>= 1; /** * 这里是为了防止多线程下调用Timer的schedule方法。Timer里面的TimerThread线程也是会用到queue的,所以这里也是为了防止定时器线程和主线程冲突 * */ synchronized(queue) { /** newTasksMayBeScheduled这个值是被写死为true的,只有任务结束,定时器完蛋的时候才会变成false */ if (!thread.newTasksMayBeScheduled) throw new IllegalStateException("Timer already cancelled."); synchronized(task.lock) { /** 只有任务还没被执行,也就是处女状态,才能执行初始化 */ if (task.state != TimerTask.VIRGIN) throw new IllegalStateException( "Task already scheduled or cancelled"); task.nextExecutionTime = time; task.period = period; task.state = TimerTask.SCHEDULED; } /** 加入队列中 */ queue.add(task); /** 如果队列中只有自己这个刚加入的任务,那就唤醒任务,立刻执行,如果还有别的任务,那这个任务就得乖乖排队执行 */ if (queue.getMin() == task) /** 因为queue.wait()方法陷入阻塞的线程只有定时器的TimerThread的实例 */ queue.notify(); } } /** * 取消定时器 * */ public void cancel() { synchronized(queue) { /** 这个状态前面已经说了很多遍了,这里要记住,因为后面任务循环会用到 */ thread.newTasksMayBeScheduled = false; queue.clear(); queue.notify(); // In case queue was already empty. } } } /*** * 用于Timer的定时器线程类 * */ class TimerThread extends Thread { boolean newTasksMayBeScheduled = true; /** 任务队列,记得Timer的构造函数么,Timer里面的TaskQueue和这个是同一个,指向同一个对象,这样子就是为了给线程传递一个共享变量 */ private TaskQueue queue; /** * 构造函数 * */ TimerThread(TaskQueue queue) { this.queue = queue; } /** * 这个不用多说你们也知道是啥吧 * */ public void run() { try { mainLoop(); } finally { /** * 这里必须要注意,Timer定时器一旦创建,只要没有发生异常,那他就是一直在不断循环等待任务执行的 * 不管你队列里面究竟还有没有任务。就算没有任务,那你能保证以后不会往这里面加任务么。 * <b>注意,如果我们想要关闭这个定时任务,那就显示调用Timer的cancel方法</b> * */ synchronized(queue) { newTasksMayBeScheduled = false; queue.clear(); // Eliminate obsolete references } } } /** * 主循环 * */ private void mainLoop() { // 死循环 while (true) { try { TimerTask task; boolean taskFired; synchronized(queue) { /** * 如果队列为空,那么就判断newTasksMayBeScheduled是不是true,如果不是,那就用queue.wait()挂起线程 * 队列为空有3种情况: * 1:刚创建,还没加任务,此时newTasksMayBeScheduled初始化为true * 2:以前有任务,但是并不是重复任务,执行完就没了,此时newTasksMayBeScheduled不变,还是true * 3:Timer的cancel()方法被调用了,清空了队列,此时newTasksMayBeScheduled被修改成false,前面有叫你们记住的 * */ while (queue.isEmpty() && newTasksMayBeScheduled) queue.wait(); if (queue.isEmpty()) break; // Timer被取消了,就退出主循环。这是两种退出方法中的第一种,是正经的方法 // 从队列中找计划执行时间最早的任务 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)) { if (task.period == 0) { // Non-repeating, remove queue.removeMin(); task.state = TimerTask.EXECUTED; } else { // 如果这是一个重复任务,那就再更改一下他的计划执行时间,并且将把这个任务按执行时间先后,重新排下序 queue.rescheduleMin( task.period<0 ? currentTime - task.period : executionTime + task.period); } } } if (!taskFired) // 如果最小的任务还没到执行时间,那就先挂起,免得抢CPU queue.wait(executionTime - currentTime); } if (taskFired) // 直接执行任务 task.run(); } catch(InterruptedException e) { /** * 结束定时器除了调用Timer的cancel()方法,还可以丢一个会报错的任务进来,不过不能保证什么时候执行,而且这样会让程序不健强,不推荐。 * 另外InterruptedException会被捕获 * */ } } } } /** * 这个是Timer中用到的队列,看不看都无所谓 * */ class TaskQueue { /** 任务数组,默认128个,但是这个任务数组会自动扩容,新容量 = 旧容量 * 2,另外要注意,整个数组所有元素都是按计划执行时间从小到大进行排序的,而且0下标是不放东西的 */ private TimerTask[] queue = new TimerTask[128]; /** 实际任务个数 */ private int size = 0; /** * 添加任务 * */ void add(TimerTask task) { /** 如果容量不够塞下这个任务,就扩容 */ if (size + 1 == queue.length) queue = Arrays.copyOf(queue, 2*queue.length); queue[++size] = task; /** 这个 */ fixUp(size); } /** * 因为本身数组就是有序的,所以这里计划执行时间最小的就是第一个元素 * */ TimerTask getMin() { return queue[1]; } /** * 移除最小的一个 * */ void removeMin() { queue[1] = queue[size]; queue[size--] = null; // Drop extra reference to prevent memory leak fixDown(1); } /** * 给最小的一个任务重新设置计划执行时间 * 因为重新设置时间后,他的计划时间可能大于后面的任务,所以会对这个任务进行降级处理 * */ void rescheduleMin(long newTime) { queue[1].nextExecutionTime = newTime; fixDown(1); } /** * 对任务进行升级处理,也就是往队首移动。将这个元素的计划执行时间和前面的进行比较,将其放到一个合适的位置,保证队列数组始终顺序排列 * @param k 要调整位置的元素的下标 * */ private void fixUp(int k) { while (k > 1) { /** 还记得我们前面说过的,队列数组的0下标是废的么,这里就是原因。k >> 1,就是k / 2 ,我们总不能是0 / 2,所以就把0下标给废了 */ int j = k >> 1; /** 这里使用的其实就是二分排序 */ if (queue[j].nextExecutionTime <= queue[k].nextExecutionTime) break; TimerTask tmp = queue[j]; queue[j] = queue[k]; queue[k] = tmp; k = j; } } /** * 和fixUp相反,这个是降级处理,就是往队尾移动 * @param k 要调整位置的元素的下标 * */ private void fixDown(int k) { int j; while ((j = k << 1) <= size && j > 0) { if (j < size && queue[j].nextExecutionTime > queue[j+1].nextExecutionTime) j++; // j indexes smallest kid if (queue[k].nextExecutionTime <= queue[j].nextExecutionTime) break; TimerTask tmp = queue[j]; queue[j] = queue[k]; queue[k] = tmp; k = j; } } }
看完源码,再看调用图,是不是感觉更了解了
推荐阅读
-
[Java 并发编程 III] 多线程案例(手撕单件模式、阻塞队列、定时器、线程池)
-
VIII.STM32F103 定时器触发器模式
-
Stm32 定时器概述和示例
-
蓝桥杯 - STM32G431RBT6(TIM 定时器的输出频率和占空比,以及详细原理和用法)
-
STM8S003F 定时器延迟
-
深入理解Java JSP中的九个内置对象及Cookie对象详解
-
理解JSP中的九大内置对象和Servlet中的Java对象
-
深入理解Java Web开发:JSP的四大作用域和九个内置对象解析
-
使用555定时器制作简单施密特触发器电路图
-
8255A和8253/8254:可编程并行接口及定时器芯片的详细解析(微机原理与系统设计笔记8)