定时器的基本使用
Java中的定时器是Timer,通过调用Timer对象的schedule方法传入任务和延迟时间。如下:
java
public static void main1(String[] args) {
Timer timer = new Timer();
timer.schedule(new TimerTask(){
@Override
public void run(){
System.out.println("1000");
}
},1000);
}
以上代码表示:在1秒后打印 1000.
运行以上代码后发现,程序一直没有退出,说明在timer对象中创建了一个线程,这个线程正在等待我们派发任务,所以一直没有退出。
定时器的模拟实现
要模拟实现定时器就需要确认3个东西:1. 创建对象来存储用户传入的任务和延迟时间。2. 使用某种数据结构来存储用户传入的任务。3. 需要一个线程不断的扫描这个数据结构中是否有任务未被执行。
那么使用哪种数据结构呢? 优先级队列是是被考虑的,因为这些定时任务的优先级就是按照延迟时间进行排序的,延迟时间越短,那么优先级就越高。
由于优先级队列中的元素是可比较的,故必须对存放延迟时间和任务的类实现Comparable接口,重写Comparator方法,通过时间戳进行比较。
所以我们创建一个MyTimerTask对象来存储用户的任务和延迟时间。如下:
java
class MyTimerTask implements Comparable<MyTimerTask>{
// 任务执行的绝对时间
private Long time;
// 任务
private Runnable runnable;
MyTimerTask(Runnable runnable ,Long time){
this.runnable = runnable;
this.time = System.currentTimeMillis()+time;
}
public Long getTime(){return this.time;}
public Runnable getRunnable(){return this.runnable;}
@Override
public int compareTo(MyTimerTask o) {
return (int)(this.time - o.time);
}
}
注意:这里没有记录用户传入的延迟时间,而是直接记录这个任务要完成的绝对的时间戳。
接下来就需要创建MyTimer类了。这个类需要包含:
- 优先级队列
- 线程(用于扫描优先级队列中是否有任务待执行)
- schedule方法(第一个参数,Runnable对象
java
class MyTimer{
private static volatile Object locker = new Object();
// 优先级队列
PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();
public void schedule(Runnable runnable,Long time){
synchronized (locker){
// 创建对象
queue.offer(new MyTimerTask(runnable,time));
// 创建好对象之后,就唤醒。
locker.notify();
}
}
// 搞一个扫描线程
public MyTimer(){
Thread 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 time = task.getTime();
Runnable runnable = task.getRunnable();
long currentTime = System.currentTimeMillis();
// 满足,
if(currentTime >= time){
runnable.run();
queue.poll();
}else {
try {
locker.wait(time-currentTime);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
});
t.start();
}
}
注意:这个扫描线程需要不停的去扫描优先级队列中受否有任务待执行,所以需要使用while循环。
注意:在判断当前的时间是否是大于定时器执行的时间时,如果不满足,那么不需要进行下一次的while循环,可以等待time-currentTime的时间。这样就可以防止忙等的情况发生。
直到优先级队列中的第一个任务完成之后,这个线程才会继续执行优先级队列中的下一个任务。这是通过MyTimerTask是通过时间戳比较器来实现的。
注意:为了防止指令重排序的情况发生和内存不可见性,建议对加锁中的代码块中要被操作的变量加上volatile关键字。防止出现意料之外的错误。