🎇个人主页:Ice_Sugar_7
🎇所属专栏:JavaEE
🎇欢迎点赞收藏加关注哦!
实现定时器
- 🍉简介
- 🍉模拟实现定时器
🍉简介
定时器类似一个闹钟,时间到了之后就会执行相应的任务
Java 标准库中已经实现了一个定时器的类 Timer
Timer timer = new Timer();
在定义好 timer 之后可以调用 schedule
把一个或多个任务(TimerTask)添加到定时器中
timer.schedule(new TimerTask() {
@Override
public 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() {
@Override
public void run() {
System.out.println("2000 ms");
timer.cancel(); //结束线程
}
},2000);
timer.schedule(new TimerTask() {
@Override
public 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();
}
@Override
public 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() {
@Override
public void run() {
System.out.println("3000 ms");
}
},3000);
myTimer.schedule(new Runnable() {
@Override
public void run() {
System.out.println("1000 ms");
}
},1000);
}
}