前言
前面我们聊了很多关于阻塞队列,单例模式等的应用,今天我们就来聊聊定时器的功能和模拟实现,其实定时器的实现在我们的日常生活中也很常见,比如说平常创建一些定时任务,定时开关机,定时去发表一篇qq空间等等,今天我们就来简单实现一个定时器.
1.JVM提供的定时器的使用
在自己实现之前,让我们先去看看JVM给我们提供好的定时器是如何使用的吧
以下是一个简单的实例
我们首先创建了一个定时器对象,定时器对象里面有一个schedule方法,这个方法最常用的是有两个参数组成的,一个是task任务,另一个是delay:相对现在的时间延迟多久发生这个事件,单位是毫秒,我们发现定时任务做完这个进程并没有结束,这是因为你没有让他结束,只能手动结束
timer也提供了一个cancel方法来执行这个操作.
其实这是因为定时器内部存在一个扫描线程会扫描任务队列,这个线程属于前台线程,会阻止进程的结束.
下面附上执行代码
public static void main(String[] args) { Timer timer = new Timer(); TimerTask task = new TimerTask() { @Override public void run() { System.out.println("确实挺不错的"); } }; timer.schedule(task,2000); TimerTask task1 = new TimerTask() { @Override public void run() { System.out.println("你们觉得怎么样"); } }; timer.schedule(task1,1000); }
2.定时器的模拟实现
首先MyTimer中应该有一个阻塞队列,一个扫描线程和一个schedule方法
private Thread t = null; private PriorityQueue<MyTimerTask> queue = new PriorityQueue<>(); public void schedule(Runnable runnable,long delay){ synchronized (lock){ MyTimerTask task = new MyTimerTask(runnable,delay); queue.offer(task); lock.notify(); } }
然后我们再想一下task的实现,首先应该有一个执行时间,由于要放入优先级队列里来实现定时器的功能,所以我们的task也需要实现compareable接口或compartor接口,我们这里使用compareable接口来实现.
然后我们是要使用扫描线程来操作和执行这个任务,所以我们应该实现Runnable接口或者持有这个接口.此时你可以理解runnable的实现就是我的任务需要做什么
我们还要提供一个run方法给下面的扫描线程来执行
class MyTimerTask implements Comparable<MyTimerTask>{ //什么时间运行这个任务 -- 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(); } //实现比较,这个地方记不得o1-o2 就去试一试 @Override public int compareTo(MyTimerTask o) { return (int)(this.time - o.time); } public long getTime(){ return time; } }
下面我们继续实现我们的定时器,我们在构造方法内部实现扫描线程对阻塞队列的拿取任务执行任务的操作,取到任务就执行,遇见空队列我们就进行阻塞,又加入任务我们就进行唤醒,于是就有了一把锁来完成对阻塞队列的执行和添加任务的操作
这里也是先获取一下距离当前时间最短的任务,看是否到时间,到达就立即取出并执行,没到就阻塞(最多阻塞时间为预计时间减去当前时间,这是因为在阻塞的期间也可能有更新的任务添加进来)
public MyTimer(){ //扫描线程 t = new Thread(()->{ while(true){ //引入锁目的是保护队列,所以得把队列的操作保护起来 synchronized (lock){ while(queue.isEmpty()){ try { lock.wait(); } catch (InterruptedException e) { throw new RuntimeException(e); } } MyTimerTask task = queue.peek(); //获取当前时间 long curTime = System.currentTimeMillis(); if(curTime >= task.getTime()){ queue.poll(); task.run(); }else{ //时间还没到也需要等待,一直执行while循环判断,称之为忙等 //这一般是一个不太好的做法 //使用sleep也不太合适 //万一在sleep过程中有线程调用了schedule //而且sleep也不能解锁 try { //新任务来被唤醒一次 //根据新的任务重新计算等待时间 //或者是wait过程中没有新的任务,但是时间到了 lock.wait(task.getTime() - curTime); } catch (InterruptedException e) { throw new RuntimeException(e); } } } } }); t.start();