🎇个人主页:Ice_Sugar_7
🎇所属专栏:JavaEE
🎇欢迎点赞收藏加关注哦!
实现定时器
- 🍉简介
- 🍉模拟实现定时器
🍉简介
定时器类似一个闹钟,时间到了之后就会执行相应的任务
Java 标准库中已经实现了一个定时器的类 Timer
Timer timer = new Timer();
在定义好 timer 之后可以调用 schedule
把一个或多个任务(TimerTask)添加到定时器中
timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("2000 ms");}
},2000);
第一个参数就是任务内容
,每个任务后面都会带有一个时间
(第二个参数),这个时间是“相对时间”
,是以 schedule 时的时间为基准,过了相对时间后才执行
比如 2000ms,它表示调用 schedule 后再过 2000ms 就会执行这个任务
TimerTask 里面有一个 run 方法,而 run 是线程的入口,说明 timer 创建了一个线程来执行任务。这个线程是前台线程,它会阻止主线程结束,需要我们使用 cancel
主动结束,否则 Timer 不知道其他地方是否会继续添加任务
public static void main(String[] args) {Timer timer = new Timer();timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("2000 ms");timer.cancel(); //结束线程}},2000);timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("1000 ms");}},1000);
}
🍉模拟实现定时器
首先要有一个数据结构负责保存 schedule 的任务(相当于任务清单),因为我们是先执行时间近的任务(比如有两个任务,一个是两点执行,另一个是两点半执行,肯定要先完成前者),换而言之,任务之间是有优先级
的,所以要用优先级队列
标准库中提供了 PriorityQueue 和 PriorityBlockingQueue,前者是线程不安全
的,后者是线程安全
的,在此处的场景中 PriorityBlockingQueue 不太好控制,容易出问题,所以我们用前者
(补充:TreeSet 和 TreeMap 虽然也是有序的,但是获取到最小值的时间复杂度为 O(logN),不及 O(1) 的优先级队列)
然后需要有一个线程不断扫描优先级队列的队首元素,看它时间到了没
public class MyTimer {private Thread t;private PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();private Object locker = new Object();public MyTimer(){//定时器构造方法的主体就是启动线程,让它去扫描队首元素t = new Thread(() -> {while (true) {synchronized (locker) {while (queue.isEmpty()) {try {locker.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}MyTimerTask task = queue.peek();long curTime = System.currentTimeMillis();if (curTime >= task.getTime()) { //时间到了,执行任务task.run();queue.poll(); //记得执行后把它出队列} else {try {locker.wait(task.getTime() - curTime); //如果还没到执行时间,那就等待,不要一直循环下去} catch (InterruptedException e) {throw new RuntimeException(e);}}}}});t.start();}public void schedule(Runnable runnable,long delay) { //把任务添加到 queue 里面synchronized (locker) {MyTimerTask task = new MyTimerTask(runnable, delay);queue.offer(task);locker.notify(); //添加元素后,就可以唤醒处于 wait 状态的线程}}
}
队列的元素——任务,它是一个类。它的成员变量应该包括时间、能让它跑起来的 Runnable 接口
public class MyTimerTask{private long time; //执行任务的时间(注意这个是“绝对时间”)private Runnable runnable; //持有 Runnable 接口可以调用它的 run 方法,也可以不持有 Runnable,而是实现 Runnable 接口并重写 run 方法MyTimerTask(Runnable runnable,long time) {this.runnable = runnable;this.time = time + System.currentTimeMillis(); //什么时候执行任务:现在的时间 + 相对时间}public void run() {runnable.run();}
}
因为优先级队列要求元素是可排序的,所以我们需要实现 Comparable 接口并重写 compareTo 方法
public class MyTimerTask implements Comparable<MyTimerTask>{private long time; //执行任务的时间(注意这个是“绝对时间”)private Runnable runnable; //持有 Runnable 接口可以调用它的 run 方法,也可以不持有 Runnable,而是实现 Runnable 接口并重写 run 方法MyTimerTask(Runnable runnable,long time) {this.runnable = runnable;this.time = time + System.currentTimeMillis(); //什么时候执行任务:现在的时间 + 相对时间}public void run() {runnable.run();}@Overridepublic int compareTo(MyTimerTask o) {return (int) (o.time-this.time); //时间小的优先级更高}
}
补充:compareTo 方法里面是 o.time-this.time 还是 this.time - o.time,不用去刻意记忆,两种都试一下就 ok 了
测试一下:
public class TestDemo {public static void main(String[] args) throws InterruptedException {MyTimer myTimer = new MyTimer();myTimer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("3000 ms");}},3000);myTimer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("1000 ms");}},1000);}
}