1.定时器的介绍
人类最早使用的定时工具是沙漏或水漏,但在钟表诞生发展成熟之后,人们开始尝试使用这种全新的计时工具来改进定时器,达到准确控制时间的目的。定时器确实是一项了不起的发明,使相当多需要人控制时间的工作变得简单了许多。人们甚至将定时器用在了军事方面,制成了定时炸弹,定时雷管。不少家用电器都安装了定时器来控制开关或工作时间。
定时器是通信协议正常运行的基本要素之一,主要用于各种定时和帧重传的任务。通信协议在单片机系统上实现所使用的定时器,定时精度要求不高,但数量要求比较大。由于硬件资源有限,不可能为每一个单独任务分配一个硬件定时器,只能通过单个硬件定时器模拟多个软件定时器的方法,来满足协议中的定时应用需要。
用一定的数据结构将这些软件定时器组织起来,并提供统一的调用接口,称为“定时器管理”。定时器管理主要有2种实现方法:
①静态数组法。将定时器节点存储在数组中。优点是逻辑简单,占用ROM较少。但这种方案有明显的缺点:当硬件定时器中断发生时,要对所有定时器节点进行减法操作,时间开销很大,且时延不确定(与定时器数目相关)。
②delta链表法。按照定时器的定时值升序排列,形成链表。后一个定时器的定时值是前面所有定时器的值加上本节点的值。这样,在每次的时钟中断处理中,只需对第1个定时器节点进行减法操作,大大减少了时间开销。但是,该方案逻辑复杂,ROM用量大,需要频繁分配回收内存,容易形成内存碎片。
定时器管理模块的设计基于静态数组法。使用一个定时器节点数组来保存所有的定时请求,数组的每一项代表一个可用的定时器节点。每一个定时器节点都有一个状态项,表示该定时器正处于空闲、使用或溢出状态。定时器的定时值和定时器超时后要发送的消息也存储在定时器节点中,从而实现用一个硬件定时器为用户提供多个软件定时器。
为了解决中断处理时间开销大的问题,在模块中引入一个辅助定时器,辅助定时器的值总是等于所有定时器节点中的最小定时值。在硬件定时器中断处理中,仅对辅助定时器进行减法操作,从而大大缩短了中断处理的时间。
2.实现代码及测试用例
完整代码:
package practice;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.LinkedList;
import java.util.List;
public class MyTimeCall {
public static void main(String[] args) throws ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchMethodException {
MyTimeCall.getIns().addTask(2, "practice.TestNode", "music1", 0);
MyTimeCall.getIns().addTask(4, "practice.TestNode", "music2", 0, "后来");
}
//实例化MyTimeCall
private static final MyTimeCall ins = new MyTimeCall();
//获取实例对象的方法
public static MyTimeCall getIns() {
return ins;
}
//任务类
private static class Task {
private long nextRunTime;
private final long delayTime;
private final Object obj;
private final Method method;
/**
* 有参构造方法
*
* @param nextRunTime 任务下一次的执行时间
* @param delayTime 延迟间隔时间
* @param obj 对象
* @param method 方法
*/
public Task(long nextRunTime, long delayTime, Object obj, Method method) {
this.nextRunTime = nextRunTime;
this.delayTime = delayTime * 1000;
this.obj = obj;
this.method = method;
}
/**
* 获取下一次任务执行的时间
*/
public void next() {
this.nextRunTime = this.nextRunTime + this.delayTime;
}
/**
* 判断当前任务能否执行
*
* @param nowTime 当前时间
* @return 布尔值
*/
public boolean needRun(long nowTime) {
return this.nextRunTime < nowTime;
}
}
//创建一个任务链表
private final List<Task> tasks = new LinkedList<>();
//标明当前任务是否已经被执行的标志
private boolean running = false;
/**
* 增加任务的方法
*
* @param delayTime 延迟间隔时间
* @param className 类名
* @param functionName 方法名
* @param conPara 方法参数
*/
public synchronized void addTask(long delayTime, String className, String functionName, Object... conPara) throws ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchMethodException {
//获取到整个类
Class<?> c = Class.forName(className);
//获取到方法参数的个数
int paraCount = conPara.length;
//遍历类的构造函数
for (Constructor<?> constructor : c.getConstructors()) {
if (constructor.getParameterCount() == paraCount) {
//如果某个构造函数的参数与传入的方法参数个数一致
Object o = constructor.newInstance(conPara);
Method method = c.getMethod(functionName);
//新建一个任务
Task task = new Task(System.currentTimeMillis() + delayTime, delayTime, o, method);
//将任务加入任务链表
tasks.add(task);
}
}
if (!this.running) {
//任务未被执行
Thread t = new Thread(this::run);
t.setName("定时器线程");
//更改任务状态
this.running = true;
t.start();
}
}
public void run() {
while (true) {
//获取当前时间
long currTime = System.currentTimeMillis();
synchronized (this) {
//遍历任务链表
this.tasks.forEach((task -> {
//判断任务当前是否可以执行
if (task.needRun(currTime)) {
//当前任务可以执行
new Thread(() -> {
try {
task.method.invoke(task.obj);
} catch (InvocationTargetException | IllegalAccessException e) {
e.printStackTrace();
}
}).start();
//设置下一次任务执行时间
task.next();
}
}));
}
//每隔多久遍历一遍任务链表
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Test {
private int id;
private String name;
public Test(int id) {
this.id = id;
}
public Test(int id, String name) {
this.id = id;
this.name = name;
}
public void music1() {
id++;
System.out.println("音乐第" + id + "次播放");
}
public void music2() {
id++;
name += "1";
System.out.println(name + "第" + id + "次播放");
}
}
测试用例结果:
定时器的实现到这里就结束啦!如果有任何问题可以在评论区留言,我会一一为你们解答。