目录
定时器的含义
定时器的使用
定时器的解析
①TaskQueue
②TimerThread
③Timer
定时器的模拟实现
①创建Task自定义类型
②创建TimerThread类
③Timer类
完整代码
定时器的含义
从名字上看,就是我们通俗理解的那个定时器.设置一定的时间,并在一定的时间后发生一定的操作.
定时器的使用
在java标准库中有一个Timer类实现了这个定时器
创建出Timer的实例后,使用Timer类中的schedule方法往其添加任务.
方法 | 说明 |
public void schedule(TimerTask task, long delay) | 在指定的延迟之后安排指定的任务执行。 |
关于第一个参数TimerTask类,其是继承了我们先前在多线程中Thread类中提到过了Runnable类.两者的使用方法和效果其实是大致相同的.
也要在TimerTask类中覆写run方法,说明要执行的任务.
值得我们注意的是,可以创建一个定时器实例后.不断了往里添加新的任务.
而不是一个定时器中只能包含有一个任务
Timer timer = new Timer();//创建一个实例对象
//调用schedule方法,并通过TimerTask的匿名内部类的方式覆写了其中的run方法
//在2秒后打印出"wow"与打印的时间点
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("wow " + new Date());
}
},2000);
//与上一致,在1秒后打印出"haha"与打印的时间点
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("haha " + new Date());
}
},1000);
定时器的解析
在java中,我们可以看到Timer类中主要为这三个类
①TaskQueue
其中TaskQueue是一个优先级队列,也称为堆.是一个以执行时间排序的小根堆.
关于使用优先级队列的原因:
时间本身就是一个很强烈的拥有前后顺序的一个数据.
假设11:00,12:00与13:00的时段分别设定一个定时器分别拥有三个任务.
在11:00时段还没有到达的时候,后面两个任务是永远永远不会先轮到他们启动的,所以对于在定时器中查看是否满足时间条件执行任务可以只观察第一个存放的数据,而不是每次都要将存储的数据全部遍历一遍来查看是否要执行相应的任务,达到了更高效的效果.
②TimerThread
在TimerThread类中,会创建出新的线程来负责检测扫描TaskQueue队列中的任务是否需要执行了.
当队列为空的时候,会调用wait方法变更为阻塞状态.
③Timer
在此类中,存放的就是Timer的构建方法,schedule方法等的实现.还会有一个标志位,判断queue队列中是否为空.
当队列为空时,TimerThread线程使用了wait方法为阻塞状态,在Timer中添加了新的数据后随即会调用相应的notify方法唤醒线程,开始扫描queue队列.
定时器的模拟实现
经过了一定解析后,我们对定时的模拟可以分成:
- 要有存放任务数据的数据结构
- 要有一个线程来负责扫描数据结构中存放的数据
因为是在定时器的实现是在多线程的环境下的,main主线程一个,自身也有一个TimerThread线程.
为了保证线程安全,我们数据结构可以使用阻塞队列来进行模拟实现.
①创建Task自定义类型
在此类中,实现的是任务类型Task的构造方法
class Task implements Comparable<Task>{
private Runnable runnable;//使用Runnable类可以达到TimerTask的效果,都是存放定时器中要执行的任务
private long time;//来记录任务执行的时间点
public Task(Runnable runnable,long time){//重写Task的构造方法
this.runnable = runnable;//创建新的Task时,要重写Runnable.
this.time = System.currentTimeMillis() + time;//因为放入参数的时间都是一个时间段,而我们需要是时间点
}
public void run(){//启动任务
runnable.run();
}
@Override
public int compareTo(Task o) {//自定义类型放入队列中,要记得重写比较方法噢
return (int)(this.time - o.time);
}
}
②创建TimerThread类
实现对存放Task的优先级阻塞队列的扫描
private PriorityBlockingQueue<Task> queue = new PriorityBlockingQueue<>();
private Object object = new Object();
class TimerThread extends Thread{
@Override
public void run() {
while(true){
try {
Task task = queue.take();//从队列中取出队首元素
long curTime = System.currentTimeMillis();
if(task.time > curTime){
//如果还没队首任务的时间则wait一段时间
//并把取出查看的任务再塞回队列
//要记住我们存放的数据结构是优先级队列,会自动排序噢
queue.put(task);
synchronized (object){
//wait记得搭配synchronized使用
//wait的时间为,当前时间到队首任务的时间之差
wait(task.time - curTime);
//这里的wait有了一段明确的时间,是不需要notify的
}
}else{
//如果时间已经到了,就可以运行任务啦
task.getRun();
}
} catch (InterruptedException e) {
e.printStackTrace();
break;//可以在这里写上break,因为while是true循环.
//加上break,中断后可以直接跳出循环,就不会卡死了.
}
}
}
}
③Timer类
实现schedule方法
public class Timer {
public Timer(){//构造方法
TimerThread thread = new TimerThread();//扫描线程
thread.start();//并启动
}
public void schedule(Runnable runnable,long time){//schedule方法,往队列中添加任务
Task task = new Task(runnable,time);//创建Task类型的任务
queue.put(task);//往队列中加入任务
synchronized (object) {//在加入任务后,需要先唤醒线程.
//此时线程可能因为还没到队首元素的时间调用了wait方法还在阻塞
//新加入的任务的时间可能比队列中队首元素的任务的时间要早
//所以需要唤醒线程重新扫描一遍队列,查看是否达到了时间
object.notify();
}
}
}
完整代码
import java.util.concurrent.PriorityBlockingQueue;
public class Timer{
class Task implements Comparable<Task>{
Runnable runnable;
long time;
public Task(Runnable runnable,long time){
this.runnable = runnable;
this.time = time;
}
public void getRun(){
runnable.run();
}
@Override
public int compareTo(Task o) {
return (int)(this.time - o.time);
}
}
///
private PriorityBlockingQueue<Task> queue = new PriorityBlockingQueue<>();
private Object object = new Object();
class TimerThread extends Thread{
@Override
public void run() {
while(true){
try {
Task task = queue.take();//从队列中取出队首元素
long curTime = System.currentTimeMillis();
if(task.time > curTime){
//如果还没队首任务的时间则wait一段时间
//并把取出查看的任务再塞回队列
//要记住我们存放的数据结构是优先级队列,会自动排序噢
queue.put(task);
synchronized (object){
//wait记得搭配synchronized使用
//wait的时间为,当前时间到队首任务的时间之差
wait(task.time - curTime);
//这里的wait有了一段明确的时间,是不需要notify的
}
}else{
//如果时间已经到了,就可以运行任务啦
task.getRun();
}
} catch (InterruptedException e) {
e.printStackTrace();
break;//可以在这里写上break,因为while是true循环.
//加上break,中断后可以直接跳出循环,就不会卡死了.
}
}
}
}
/
public Timer(){
TimerThread thread = new TimerThread();
thread.start();
}
public void schedule(Runnable runnable,long time){
Task task = new Task(runnable,time);
queue.put(task);
synchronized (object) {
object.notify();
}
}
public static void main(String[] args) {
Timer timer = new Timer();
timer.schedule(new Runnable() {
@Override
public void run() {
System.out.println("wow");
}
},1000);
timer.schedule(new Runnable() {
@Override
public void run() {
System.out.println("hahaha");
}
},2000);
}
}