定时器是什么
定时器是软件开发中的一个重要组件.类似于一个"闹钟".达到一个设定的时间之后,就执行某个指定好的代码.
定时器是一种实际开发中非常常用的组件.
比如网络通信种,如果对方500ms内没有返回数据,则断开尝试重连.
比如一个Map,希望里面的某个key在3s之后过期(自动删除)
类似于这样的场景就需要用到定时器.
标准库中的定时器
标准库中提供了一个Timer类.Timer类的核心方法尾schedule.
schedule包含两个参数.第一个参数指定即将要执行的任务代码,第二个参数指定多长时间之后执行(单位为毫秒)
下面是代码示例:
public class TestTimer {
public static void main(String[] args) {
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("hello timer");
}
}, 3000);
System.out.println("hello main");
}
}
运行代码,我们可以观察到一上来直接打印"hello main",然后三秒之后执行了"hello timer",为什么会出现这种情况呢?显然是每个Timer内置了一个线程.
注:由于Timer内置了线程(前台线程),会阻止进程结束.这是因为timer不知道代码是否还会添加新的任务进来,就处于一种严阵以待的状态.
所以此处需要使用cancel()方法来主动结束,否则timer不知道其它什么地方还会添加任务.
public class TestTimer {
public static void main(String[] args) throws InterruptedException {
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("hello timer");
}
}, 3000);
System.out.println("hello main");
Thread.sleep(4000);
timer.cancel();
}
}
这时就可以观察到进程结束成功了.
实现定时器
定时器的构成
一个带优先级队列(不要使用PriorityBlockingQueue,容易死锁!),而是使用PriorityQueue
实现原理:
1.队列中每个元素是一个Task对象.
2.Task中带有时间属性,队首元素就是即将要执行的任务
3.同时有一个worker线程一直在扫描队首元素,看队首元素是否需要执行(先执行时间小的,后执行时间大的).
1.Timer类提供的核心接口为schedule,用于注册一个任务,并指定这个任务多长时间后执行.
public class MyTimer {
public void schedule(Runnable runnable, long time) {
//TODO
}
}
2.Task类用于描述一个任务(作为Timer的内部类).里面包含着一个Runnable对象和一个time(毫秒时间戳)
这个对象需要放到优先级队列中,因此需要实现Comparable接口.
PriorityQueue, TreeMap, TreeSet都要求元素是"可比较大小的".需要Comparable,Comparator.
HashMap, HashSet则是要求元素是"可比较相等的","可hash的".因此需要实现
equals,hashCode方法.
class MyTimerTask implements Comparable<MyTimerTask> {
private Runnable runnable;
//为了方便后续判定,使用了绝对的时间戳
private long time;
public MyTimerTask(Runnable runnable, long delay) {
this.runnable = runnable;
//取当前时刻的时间戳 + delay,作为该任务实际执行的时间戳
this.time = System.currentTimeMillis() + delay;
}
@Override
public int compareTo(MyTimerTask o) {
//这样的写法意味着每次去除的是时间最小的元素
//到底是谁减谁,不要记,建议随便写个试一试
return (int)(this.time - o.time);
}
}
3.Timer实例中,通过PriorityQueue来组织若干个Task对象.
通过schedule往队列中插入一个个Task对象
class MyTimer {
//核心结构
private PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();
//创建一个锁对象
private Object locker = new Object();
public void schedule(Runnable runnable, long time) {
//根据参数,构造MyTimerTask,插入队列即可
synchronized(locker) {
MyTimerTask myTimerTask = new MyTimerTask(runnable, delay);
queue.offer(myTimerTask);
locker.notify();
}
}
}
4.Timer类中存在一个worker线程,一直不停地扫描队首元素,看看是否能执行这个任务.
所谓能执行就是时间已经到达了.
//在这里构建线程,负责执行具体的任务了
public MyTimer() {
Thread t = new Thread(() -> {
while(true) {
try {
synchronized(locker) {
//阻塞队列,只有阻塞的入队列和阻塞的出队列,没有阻塞查看队首元素
while(!queue.isEmpty()) {
locker.wait();
}
MyTimerTask myTimerTask = queue.peek();
long curTime = System.currentTimeMillis();
if(curTime >= myTimerTask.getTime()) {
//时间到了,可以执行任务了
queue.poll();
myTimerTask.run();
} else {
//时间还没到
locker.wait(myTimerTask.time - curTime);
}
}
} catch(InterruptException e) {
e.printStackTrace();
}
}
});
t.start();
}
下面附上完整代码以及解析:
import java.util.PriorityQueue;
//通过这个类,来描述一个任务,这一整个任务,是要放到优先级队列中的
class MyTimerTask implements Comparable<MyTimerTask>{
//在什么时间来执行这个任务
//此处的time是一个ms级别的时间戳
private long time;
//实际任务要执行的代码
private Runnable runnable;
//delay期望是一个相对时间
public MyTimerTask(Runnable runnable, long delay) {
this.runnable = runnable;
//计算一下要执行任务的绝对时间.(使用绝对时间,方便判定任务是否到达时间的)
this.time = System.currentTimeMillis() + delay;
}
//实际执行代码的方法(一般在主函数中重写)
public void run() {
runnable.run();
}
//用于获得任务执行时间
public long getTime() {
return this.time;
}
//构建一个比较器(因为在优先级队列中是按时间对任务进行比较)
@Override
public int compareTo(MyTimerTask o) {
return (int)(this.time - o.time);
}
}
public class MyTimer{
//构建一个线程
private Thread t = null;
//创建存放任务的主体--优先级队列
private PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();
//创建一个锁对象
private Object locker = new Object();
//结束进程的方法
public void cancel() {
t.interrupt();
}
//构造方法:以用于创建线程
public MyTimer() {
t = new Thread(() -> {
while(!Thread.interrupted()) {
try {
synchronized (locker) {
while(queue.isEmpty()) {
//当队列为空时,这个线程就一直处于阻塞等待的状态
locker.wait();
}
//获得队列头部元素
MyTimerTask task = queue.peek();
//计算系统当前时间
long curTime = System.currentTimeMillis();
//判断是否应该执行
if(curTime >= task.getTime()) {
//执行任务
queue.poll();
task.run();
} else {
//等待到指定时间再执行任务
locker.wait(task.getTime() - curTime);
}
}
} catch (InterruptedException e) {
break;
}
}
});
t.start();
}
//通过这个方法来执行实际的任务
public void schedule(Runnable runnable, long delay) {
synchronized (locker) {
//先创建一个任务,后将任务放入队列
MyTimerTask myTimerTask = new MyTimerTask(runnable, delay);
queue.offer(myTimerTask);
//唤醒因为队列中因为没有任务而阻塞等待的线程
locker.notify();
}
}
}