1.定时器的定义
定时器也是软件开发中的一个重要组件. 类似于一个 "闹钟". 达到一个设定的时间之后, 就执行某个指定好的代码.
定时器是一种实际开发中非常常用的组件.类似于下方的场景就需要用到定时器:
- 比如网络通信中, 如果对方 500ms 内没有返回数据, 则断开连接尝试重连.
- 比如一个 Map, 希望里面的某个 key 在 3s 之后过期(自动删除).
2.标准库中的定时器
2.1构造方法
2.2成员方法
- 标准库中提供了一个 Timer 类. Timer 类的核心方法为 schedule 。
- schedule 包含两个参数. 第一个参数指定即将要执行的任务代码(Mytask), 第二个参数指定多长时间之后执行 (单位为毫秒)。
schedule方法参数列表: void schedule(TimerTask task, long delay)
作用:在指定的延迟之后安排指定的任务执行。
实现一个简单的定时器使用:
import java.util.Timer; import java.util.TimerTask; public class testDemo1 { public static void main(String[] args) { Timer timer=new Timer(); timer.schedule(new TimerTask() { @Override public void run() { System.out.println("hello4"); } },4000); timer.schedule(new TimerTask() { @Override public void run() { System.out.println("hello3"); } },3000); timer.schedule(new TimerTask() { @Override public void run() { System.out.println("hello2"); } },2000); System.out.println("hell01"); } }
上方代码,是让在定时器中执行4个任务,分别等待时间4s,3s,2s,1s,打印内容。
运行结果如下:
观察结果,定时器通过对延迟时间的排序完成了打印,再仔细看,程序并没有停止,什么时候程序不会停止呢?就是前台线程没有执行完的时候程序不会停止,也就是说定时器中的创建的线程是前台线程,并且在完成任务后,不会立即停止。
3.模拟实现一个定时器
定时器的主要构造内容:
public class MyTimer{
//1.一个优先级阻塞队列
//2.一个内部类Mytask(服务于schedule方法)
//3.schedule方法
//4.一个构造方法
//5.一个锁
}
定时器中核心数据结构是优先级阻塞队列(PriorityBlockingQueue),它能够帮助我们判断谁先执行,谁后执行。
schedule()方法
schedule()方法是定时器的核心方法,它相当于一个时间表,安排每一个任务的时间。
该方法有两个参数:1.runnable(任务) 2.delay(延迟时间)
因为需要将两个参数同时加入到 优先级阻塞队列(PriorityBlockingQueue)当中,因此我们需要将其进行封装一下,代码如下:
class MyTask implements Comparable<MyTask>{ public Runnable runnable; public long time; public MyTask(Runnable runnable, long time) { this.runnable = runnable; this.time =System.currentTimeMillis()+time; } @Override public int compareTo(MyTask o) { return (int)(this.time-o.time); } }
在使用延迟时间时,我们现在使用绝对时间来记录,这里使用到了时间戳,并且实现Comparable<>接口,他的目的是告诉阻塞队列,根据时间判断优先执行哪一个任务。
进行来看schedule方法的实现:
public void schedule(Runnable runnable,long delay) { MyTask task=new MyTask(runnable,delay); queue.put(task); //来新任务了,就叫醒线程 synchronized (locker){ locker.notify(); } }
代码解读是:构造一个任务,并且设定它的延迟时间,将任务加入到优先级阻塞队列,下方的notify方法配合构造方法中的wait使用(下方会有解读)。
构造方法
代码实现如下:
public MyTimer() { Thread t=new Thread(()->{ while (true) { synchronized (locker) { try { MyTask cur = queue.take(); long curTime = System.currentTimeMillis(); if (curTime <= cur.time) { //没到时间 queue.put(cur); locker.wait(cur.time - curTime); } else { //时间到了 cur.runnable.run(); } } catch (InterruptedException e) { e.printStackTrace(); } } } }); t.start(); }
代码解读:
我们先取出队列的队首元素,在调用出现在的时间戳,判断队首元素是否达到时间了,因为优先级阻塞队列里面没有peek方法,我们每次比较都必须将队首元素拿出来,若没有达到指定时间,我们需要再将队首元素再次塞进队伍当中,塞回去之后,若时间到了,就执行这个任务的run方法。
在判断后,时间没到时,我们不能再一直进行判断时间,因为这个cpu即没干任何事,也没有得到休息(cpu忙等),这时我们就可以使用wait方法,让cpu先将资源释放了,等待唤醒就行了,只要参数中的时间到了或者有需要比现在任务优先级更高的任务需要执行,就让他先执行。
4.MyTimer完整代码
import java.util.concurrent.PriorityBlockingQueue; //没个任务都有一个需要做的事 class MyTask implements Comparable<MyTask>{ public Runnable runnable; long time; public MyTask(Runnable runnable, long time) { this.runnable = runnable; this.time =System.currentTimeMillis()+time; } @Override public int compareTo(MyTask o) { return (int)(this.time-o.time); } } public class MyTimer { //核心数据结构,优先级阻塞队列 private PriorityBlockingQueue<MyTask> queue=new PriorityBlockingQueue<>(); private Object locker=new Object(); public void schedule(Runnable runnable,long delay) { MyTask task=new MyTask(runnable,delay); queue.put(task); //来新任务了,就叫醒线程 synchronized (locker){ locker.notify(); } } public MyTimer() { Thread t=new Thread(()->{ while (true) { synchronized (locker) { try { MyTask cur = queue.take(); long curTime = System.currentTimeMillis(); if (curTime <= cur.time) { //没到时间 queue.put(cur); locker.wait(cur.time - curTime); } else { //时间到了 cur.runnable.run(); } } catch (InterruptedException e) { e.printStackTrace(); } } } }); t.start(); } }