目录
什么是定时器?
如何使用定时器?
schedule
Timer的构造方法
cancel
定时器的模拟实现
思路分析
实现过程
完整代码
定时器:即在设定的时间时执行某事的设备(例如闹钟,在指定的时间响铃),Java中的定时器会在到达设定的时间后,执行指定的代码
Java标准库中提供了一个定时器 Timer类 供我们使用,位于java.util中
对于Timer类,其核心方法为schedule
public void schedule(TimerTask task, long delay)
其中包含两个参数,
TimerTask是一个抽象类,其子类是一个可以被Timer执行的任务,要执行的任务代码在run()方法中实现
task 即到达时间后要执行的任务代码,其必须是 TimerTask 的子类,通过继承TimerTask类并重写run()方法来指定具体的任务
delay即指定要等待的时间(单位为毫秒)
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("3000");
}
},3000);//3秒后打印3000
public void schedule(TimerTask task, Date time)
在time时间执行task任务一次
public void schedule(TimerTask task, long delay, long period)
在delay后执行task一次,之后每period时间后执行task
public void schedule(TimerTask task, Date firstTime, long period)
在firstTime时执行task一次,之后每period时间后执行task,若时间为过去时间,则会立即执行
Timer timer = new Timer()
public Timer() { this("Timer-" + serialNumber()); }
调用this("Timer-" + serialNumber()),以 Timer- +序列号作为定时器的名字
Timer timer = new Timer(String name) 以name作为定时器的名字
Timer timer = new Timer(boolean isDeamon) 是否将该定时器作为守护线程执行
Timer timer = new Timer(String name, boolean isDeamon) 以name作为定时器名字,是否将该定时器作为守护线程
cancel方法用于终止Timer线程
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
//定时器的使用
public class Demo {
public static void main(String[] args) throws InterruptedException {
Timer timer = new Timer();
Date date = new Date(System.currentTimeMillis());
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("hello");
}
},date,2000);
Thread.sleep(5000);//若主线程不休眠,直接执行cancel方法,则定时器还来不及执行就被关闭了
timer.cancel();
}
}
注:
1. 每一个Timer仅对应一个线程,而不是每调用一次schedule就创建一个线程
2. Timer是线程安全的
了解了什么是定时器和定时器的使用之后,那么定时器是如何实现的呢?
我们通过模拟实现定时器来进一步了解定时器的原理
这里我们仅模拟 Timer 不带参数的构造方法 和 等待delay时间后执行task的schedule
要想实现定时器,首先我们要分析定时器需要完成的功能,以及如何实现这些功能
Timer类通过schedule添加等待delay时间后执行的任务代码,因此我们需要一个容器来存放这些任务,且先到达指定时间的代码先执行,因此我们可以使用优先级队列来存放 task(其中task带有时间属性,记录任务执行的时间),队首元素是最先执行的任务
同时,我们也需要有一个线程来扫描队首元素,判断队首元素是否需要执行
因此,模拟定时器需完成:
1. 优先级队列,用于存放task,队首元素是最先执行的任务
2. task中带有时间属性,记录task执行的时间
3. 线程worker 扫描队首元素,判断队首元素是否需要执行
4. 保证定时器线程安全
我们首先创建MyTimer:
class MyTimer{
private Thread worker = null;//用来扫描队首元素,判断其是否需要执行
//任务队列,优先执行时间短的任务线程
private PriorityQueue queue = new PriorityQueue<>();
public void schedule(Runnable runnable, long delay){
}
}
MyTimer中包含一个优先级队列,其中存放任务task,因此我们创建MyTimer类:
MyTimerTask类用于描述一个任务(作为Timer的内部类),里面包含一个Runnable对象和一个time(毫秒时间戳)(由于传入的delay是等待时间,因此要将其转换为执行时间)
class MyTimerTask{
private long time;//ms级别的时间戳
private Runnable runnable;//要执行的代码
//构造方法
public MyTimerTask(Runnable runnable, long delay){
this.runnable = runnable;
//计算要执行的时间
this.time = System.currentTimeMillis() + delay;
}
//run方法
public void run(){
runnable.run();
}
public long getTime(){
return time;//返回时间戳
}
}
由于MyTimerTask对象要放到优先级队列中,因此必须可比较,这里我们实现Comparable接口,使其可以进行比较
重写其中的compareTo方法,让执行时间小的元素优先出队列
@Override
public int compareTo(MyTimerTask o) {
return (int)(this.time - o.time);
}
接下来我们实现schedule方法,
schedule方法要实现的功能为:将新增的任务添加到队列中
//通过schedule,添加要执行的线程
public void schedule(Runnable runnable, long delay){
MyTimerTask task = new MyTimerTask(runnable, delay);
//将任务添加到队列中
queue.offer(task);
}
然后我们来实现MyTimerTask的构造方法:
在构造方法中,我们要实现的功能有:
1. 扫描队首元素,判断其是否到达执行时间,若到达执行时间,就执行任务代码,若未到达时间,则等待
由于我们要保证线程安全,因此我们需要相关操作进行加锁
在这里,我们通过创建锁对象进行加锁(也可以通过this进行加锁)
我们实现实现worker扫描队首元素
worker要反复扫描队首元素,然后判断队首元素是否到达指定时间
public MyTimer(){
worker = new Thread(()-> {
//反复扫描队首元素,然后判定队首元素是否到时间
//未到时间,等待
//到时间,执行任务并将其从任务队列中删除
while (true) {
if (queue.isEmpty()) {
//队列为空,要等待添加任务
}
//队列不为空,获取队首元素
MyTimerTask task = queue.peek();
//获取当前时间
long curTime = System.currentTimeMillis();
//判断是否到任务时间
if (curTime >= task.getTime()) {
task.run();
queue.poll();
} else {
//未到任务执行时间,等待
}
}
});
worker.start();
}
在判断队列为空时,要等待调用schedule方法向队列中添加元素后才解除阻塞状态,因此我们可以使用wait()方法,等待schedule唤醒,然后也可能由于其他原因被意外唤醒,因此我们使用while循环来判断队列是否为空,在结束阻塞状态后,再进行一次判断,保证队列不为空
而在未到达任务时间时,则使用 wait(task.getTime() - curTime) 等待指定时间后再解除阻塞状态,然后再进行判断。而若是在等待期间插入了新的任务,也需要解除阻塞状态,判断新插入的是否需要先执行、是否到达执行时间
因此,在schedule方法中,完成添加任务操作后,需要唤醒阻塞的线程
而在执行添加任务操作时,也需要进行加锁,保证线程安全
public void schedule(Runnable runnable, long delay){
synchronized (locker) {
MyTimerTask task = new MyTimerTask(runnable, delay);//将任务添加到队列中
queue.offer(task);
//将任务添加到队列中后,就可以唤醒阻塞的扫描线程了
locker.notify();
}
}
接下来,我们对构造方法中的判断和执行操作进行加锁
public MyTimer(){
worker = new Thread(()->{
//扫描线程反复扫描队首元素,然后判定队首元素是否到时间
//未到时间,阻塞
//到时间,执行任务并将其从任务队列中删除
while (true) {
try{
synchronized (locker) {
while (queue.isEmpty()) {
//阻塞等待
locker.wait();
}
MyTimerTask task = queue.peek();
//获取当前时间
long curTime = System.currentTimeMillis();
//判断是否到任务时间
if (curTime >= task.getTime()) {
task.run();
queue.poll();
} else {
//阻塞等待
locker.wait(task.getTime() - curTime);
}
}
}catch (InterruptedException e){
e.printStackTrace();
}
}
});
worker.start();
}
MyTimerTask:
class MyTimerTask implements Comparable{
private long time;//ms级别的时间戳
private Runnable runnable;//要执行的代码
//构造方法
public MyTimerTask(Runnable runnable, long delay){
this.runnable = runnable;
//计算要执行的绝对时间
this.time = System.currentTimeMillis() + delay;
}
//run方法
public void run(){
runnable.run();
}
public long getTime(){
return time;//返回时间戳
}
//重写compareTo,通过时间进行比较
@Override
public int compareTo(MyTimerTask o) {
return (int)(this.time - o.time);
}
}
MyTimer:
//模拟实现定时器
class MyTimer{
private Thread worker = null;//用来扫描队首元素,判断其是否需要执行
//任务队列,优先执行时间短的任务线程
private PriorityQueue queue = new PriorityQueue<>();
//创建锁对象
private Object locker = new Object();
//通过schedule,添加要执行的线程
public void schedule(Runnable runnable, long delay){
synchronized (locker) {
MyTimerTask task = new MyTimerTask(runnable, delay);//将任务添加到队列中
queue.offer(task);
//将任务添加到队列中后,就可以唤醒阻塞的扫描线程了
locker.notify();
}
}
//构造方法,创建扫描线程,让扫描线程进行判定和执行
public MyTimer(){
worker = new Thread(()->{
//扫描线程反复扫描队首元素,然后判定队首元素是否到时间
//未到时间,阻塞
//到时间,执行任务并将其从任务队列中删除
while (true) {
try{
synchronized (locker) {
while (queue.isEmpty()) {
//阻塞等待
locker.wait();
}
MyTimerTask task = queue.peek();
//获取当前时间
long curTime = System.currentTimeMillis();
//判断是否到任务时间
if (curTime >= task.getTime()) {
task.run();
queue.poll();
} else {
//阻塞等待
locker.wait(task.getTime() - curTime);
}
}
}catch (InterruptedException e){
e.printStackTrace();
}
}
});
worker.start();
}
}