目录
1.介绍定时器
1.1.定时器概念
(1)“定时器”类似闹钟,在指定的时间会触发一定的操作
(2)多线程中的定时器,是通过指定一个任务(Runnable),并且加上一个时间(例如:3000ms);这个任务不会立即被执行,而是会等待3000ms后才会开始执行
(3)上面这种效果称为:延时执行/定时执行
1.2.定时器使用案例
在平时生活中,有许多案例都是运用这种思想,比如验证码、某些链接等等,下面我们介绍短信验证码。
(1)向我们平时遇到的验证码有效时间,都是60s;当超过了这个时间,才输入验证码,就会发生失效。
(2)在发生短信的时候,生成一个验证码并且保存起来,并且设置一个时间(60s),在60s之后,就会执行一个:删除该验证码 的任务,此时,验证码就无效了。
而我们标准库中,也有可供我们使用的定时器。
2.使用定时器
2.1.认识定时器的使用
(1)定时器的类是:Timer
(2)实例化一个定时器对象
Timer timer = new Timer();
这就是实例化好了一个定时器对象。
(3)定时器中的方法
方面名如下
schedule();
这个方法就是用来向定时器中添加任务和时间,当超过那么多时间之后,定时器中的线程就会执行该任务。
参数介绍:
这就是关于定时器最基本的认识了,下面介绍一下如何使用。
2.2.使用定时器
(1)实例化定时器对象
Timer timer = new Timer();
(2)向任务方法添加任务
public static void main(String[] args) {
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("我是Runnable的子类,5000");
}
},5000);
}
这样就完成了任务和时间的设置,看一下运行结果:
效果也是非常的ok啊,接下来添加三个不同的时间的任务,看一下效果。
(3)不同时刻的任务
public static void main(String[] args) {
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("我是Runnable的子类,5000");
}
},5000);
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("我是Runnable的子类,3000");
}
},3000);
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("我是Runnable的子类,1000");
}
},1000);
}
运行结果:
总结一下使用定时器的步骤:1)实例化一个定时器对象 2)调用定时器对象中的方法,向里面添加任务和指定的时间。到达一定的时间就会自动执行该任务。
3.模拟实现定时器
上述我们使用了标准库中的定时器,现在,我们要自己模拟实现一个定时器。‘
实现定时器需要的东西:(1)可以延时执行任务(2)可以管理多个任务
得到一个大致的框架:
执行任务-->线程。管理多个任务-->使用数据结构。延时执行任务-->不断循环
我们需要模拟实现Timer。有构造方法、schedule方法
基本步骤也就是下面的两步:
(1)任务类
我们知道系统提供的定时器,是可以将任务提交进去,到达规定的时间内就会执行,所以,我们模拟实现也需要可以保存任务的数据结构。
第一、我们想到使用任务队列,也就是阻塞队列
第二、使用优先级队列
这样一对比,虽然阻塞队列看起来线程安全,但是不一定好。我们选择优先级队列,原因是它本身带有优先级,也就是可以让时间少的任务先被执行,也就可以完成按时间顺序执行的效果;至于线程安全问题,我们加锁就好了。
下面就是一个任务类:
class MyTimerTask {//这个类用来保存任务和时间
private Runnable runnable;
private long time;//这里的时间和delay不一样,这里的是到达某个时间点就开始执行任务,而后者是等过了多少分钟后
public MyTimerTask(Runnable runnable,long time) {
this.runnable = runnable;
this.time = System.currentTimeMillis() + time;//需要换算一下时间
}
public void run() {
//用于执行任务
runnable.run();
}
public long getTime() {
return time;//返回时间。例如:14:00
}
}
要想让它可以按照时间的顺序执行,就需要这个任务可以记录时间,也就是带有时间属性。但是这个任务类是我们自己定义的,直接存入优先级队列中是不行的,所以我们要实现Comparator接口,让其可以比较。
class MyTimerTask implements Comparable<MyTimerTask>{//这个类用来保存任务和时间
private Runnable runnable;
private long time;//这里的时间和delay不一样,这里的是到达某个时间点就开始执行任务,而后者是等过了多少分钟后
public MyTimerTask(Runnable runnable,long time) {
this.runnable = runnable;
this.time = System.currentTimeMillis() + time;//需要换算一下时间
}
public void run() {
//用于执行任务
runnable.run();
}
public long getTime() {
return time;//返回时间。例如:14:00
}
@Override
public int compareTo(MyTimerTask o) {
return (int)(this.time - o.time);
}
}
这就是任务类的实现
(2)Timer类
这个类的表明:实例化Timer对象,并且调用该对象中的方法,将任务和时间提交进去。
实质作用:可以接收任务,也就是可以保存任务;可以执行任务,也就要有线程
下面就是一个初步的框架:
class MyTimer {
//保存任务
private PriorityQueue<MyTimerTask> priorityQueue = new PriorityQueue<>();
//构造方法中内置线程,用于执行任务
public MyTimer() {
Thread t = new Thread(()->{
try {
while (true) {//线程不断的执行
if(priorityQueue.size() == 0) {
continue;//暂时没有任务,进入下一轮循
}
MyTimerTask task = priorityQueue.peek();//取出堆顶对象
long Time = task.getTime();//获取该任务需要执行的时间点,比如14:00
if(System.currentTimeMillis() >= Time) {//到达时间就执行
//比如现在的时间是14:20,就会进入if
task.run();//执行任务
priorityQueue.poll();
}
}
}catch (InterruptedException e) {
e.printStackTrace();
}
});
t.start();
}
//用于添加任务
public void schedule (Runnable runnable,long time) {
MyTimerTask task = new MyTimerTask(runnable,time);
priorityQueue.offer(task);//将任务对象保存到优先级队列中
}
}
}
这样的定时器也是可以使用的,但是存在两个问题:第一个就是线程安全问题,第二个就是当任务未到时间时,cpu一直处于空转的状态(也就是while循环一直在进行),所以我们需要进行改进。
所以我们需要引入锁和线程等待机制
class MyTimer {
private PriorityQueue<MyTimerTask> priorityQueue = new PriorityQueue<>();//保存任务
private Object locker = new Object();
public MyTimer() {//构造方法、一创建就开始等待执行线程
Thread t = new Thread(()->{
try {
while (true) {//线程不断的执行
synchronized (locker) {//对 操作队列的操作进行加锁
if(priorityQueue.size() == 0) {
//continue;//暂时没有任务,进入下一轮循环
locker.wait();//没有任务时,阻塞等待
}
MyTimerTask task = priorityQueue.peek();//取出堆顶对象
long Time = task.getTime();//获取该任务需要执行的时间点,比如14:00
if(System.currentTimeMillis() >= Time) {//到达时间就执行
//比如现在的时间是14:20,就会进入if
task.run();//执行任务
priorityQueue.poll();
}else {
locker.wait(Time - System.currentTimeMillis());//等待指定的时间,否则死循环
}
}
}
}catch (InterruptedException e) {
e.printStackTrace();
}
});
t.start();
}
public void schedule (Runnable runnable,long time) {//用于添加任务
synchronized (locker) {//对操作对象的进行加锁
MyTimerTask task = new MyTimerTask(runnable,time);//每次的任务和时间就创建一个对象
priorityQueue.offer(task);//将任务对象保存到优先级队列中
locker.notify();//当有新任务进来时,就唤醒wait
}
}
}
这就是完整的代码,我们配合上述的任务类,允许一下代码:
测试类:
代码运行结果:
嗯,很完美!