定时器是日常开发中很常见的组件,定时器大家可能不知道是干什么的,但是定时炸弹肯定都听过,定个时间,过一段时间后bomb!!!爆炸
定时器的逻辑和这个一样,约定一个时间,这个时间到达之后,执行某个代码逻辑;定时器的常见场景有网络通信,定时邮件发送等等。
计算机网络中的“超时重传”就用到了定时器。当客户端向服务器发送消息时,服务器可能由于某些问题一直不回复,此时该怎么办呢?肯定不能无限的等,需要有一个最大的期限,当到达这个最大期限时,该放弃呢?还是重传呢?或者想别的解决办法,这时就用到了定时器。
内部库Timer
当然,不光要学会怎么使用内部库提供的定时器,我们还要自己手写一个定时器出来。
怎么写呢?
1、需要一个线程,不断扫描是否有任务到达时间,可以执行了。
2、需要一个数据结构,存储所有的任务。
3、还需要创建一个类,通过类的对象来描述一个任务(至少要包含做什么和时间)。
那么又出现一个问题,该使用什么数据结构呢?
用数组吗?不行,用数组每次扫描都要遍历所有任务,时间开销太大;
想想我们学过的数据结构,每次执行时间最小的,是的,没错,就是它,它就是--优先级队列!
优先级队列每次放入元素时都会更新顺序,保证时间最小的一定在最前面,因为我们每次可以执行的一定是时间最小的,之后的元素都不需要搜索,所以时间复杂度是O(1)。
代码如下:
package Thread;
import java.util.PriorityQueue;
import java.util.Timer;
import java.util.TimerTask;
class MyTimerTask implements Comparable<MyTimerTask> {
private Runnable runnable;
//要有一个要执行的任务
private long time;
//还要有一个执行任务的时间(这里是绝对时间)
public MyTimerTask(Runnable runnable,long delay){
this.runnable = runnable;
this.time = System.currentTimeMillis() + delay;
}
@Override
public int compareTo(MyTimerTask o){
return (int)(this.time - o.time);
//这样的写法,就是让队首元素是最小时间的值
}
public long getTime(){
return time;
}
public Runnable getRunnable(){
return runnable;
}
}
class MyTimer{
private PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();
private Object locker = new Object();
public void schedule(Runnable runnable,long delay){
synchronized (locker){
queue.offer(new MyTimerTask(runnable,delay));
locker.notify();
}
}
public MyTimer(){
Thread t = new Thread(() -> {
while(true){
try{
synchronized (locker){
while(queue.isEmpty()){
locker.wait();
}
MyTimerTask task = queue.peek();
long curTime = System.currentTimeMillis();
if(curTime >= task.getTime()){
task.getRunnable().run();
queue.poll();
}
else{
locker.wait(task.getTime() - curTime);
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
}
}
public class mtime {
public static void main(String[] args) {
MyTimer timer = new MyTimer();
timer.schedule(new Runnable() {
@Override
public void run() {
System.out.println("3000");
}
},3000);
timer.schedule(new Runnable() {
@Override
public void run() {
System.out.println("2000");
}
},2000);
timer.schedule(new Runnable() {
@Override
public void run() {
System.out.println("1000");
}
},1000);
System.out.println("程序开始执行");
}
}
这里为什么要用wait呢?用sleep可以吗?
答案是不可以!
当我们向队列中插入元素时,会调用notify方法,这里使用wait是为了当新插入队列中的元素的时间比当前队头的元素的时间小时,就需要进行更新,重新判定一下最早的任务以及此处的等待时间。