定时器是软件开发中的一个重要组件,类似于一个"闹钟"当达到一个设定的时间之后,就执行某个指定好的代码(任务)。
Timer
JAVA标准库中已经为我们实现了一个定时器,我们直接new就行了。
Timer timer = new Timer();
Timer类中最重要的一个方法就是schedule(),这个方法用于设置定时器待执行的任务和执行任务的时间。
public static void main(String[] args) {
Timer timer = new Timer();
//在3秒后打印3000
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("3000");
}
}, 3000);
//在2秒后打印2000
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("2000");
}
}, 2000);
//在1秒后打印1000
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("1000");
}
}, 1000);
System.out.println("OK");
}
可以发现在代码执行完毕后程序并没有结束,这是因为虽然主线程结束了但是Timer类中的线程在阻止程序结束,它们还在等待新的任务进来被执行。
为了可以更好的理解定时器的原理,下面进行简单的模拟实现。
模拟实现
首先我们需要先创建一个MyTimerTask类,该类主要用来保存待执行的任务,和执行该任务的时间点。
class MyTimerTask {
public Runnable runnable;
//存储绝对时间,后期直接和当前时间比较大小就行
public long time;
public MyTimerTask(Runnable runnable, long time){
this.runnable = runnable;
this.time = time + System.currentTimeMillis();
}
}
接着写我们的定时器类MyTimer。首先我们应该先选择一种数据结构来存储所有的MyTimerTask,这里我推荐使用优先级队列,也就是堆。因为如果使用数组或链表来存储就需要你不断地遍历该数组/链表,而使用优先级队列就可以只检查队首元素是否到执行时间。
public class MyTimer {
private final PriorityQueue<MyTimerTask> priorityQueue = new PriorityQueue<>();
}
因为我们使用了优先级队列所以我们需要让MyTimerTask类可以进行比较。
class MyTimerTask implements Comparable<MyTimerTask>{
……
@Override
public int compareTo(MyTimerTask o) {
return (int) (this.time-o.time);
}
}
接着我们需要实现一个schedule方法可以接收定时任务和时间,因为是多线程代码所以我们还应该加一个属性用来充当锁对象。
public class MyTimer {
private final PriorityQueue<MyTimerTask> priorityQueue = new PriorityQueue<>();
private Object lock = new Object();
public void schedule(Runnable runnable, long time) {
synchronized (lock) {
priorityQueue.add(new MyTimerTask(runnable, time));
}
}
}
此时我们还需要一个自动检查队列的线程,而且该线程并不需要程序员来启动和创建,所以我们可以将它写在构造方法中。
public MyTimer() {
Thread t = new Thread(()->{
while(true) {
synchronized (lock) {
if (priorityQueue.isEmpty()) {
//堆为空
}
MyTimerTask myTimerTask = priorityQueue.peek();
if (myTimerTask.time <= System.currentTimeMillis()) {
//执行当前任务
myTimerTask.runnable.run();
//将当前任务移除
priorityQueue.poll();
}else {
//时间没到
}
};
}
});
t.start();
}
此处我们可以用阻塞队列的思想,当堆为空或者执行时间没到就使用wait()进行等待。有人添加任务时就使用notify()进行唤醒线程。
public class MyTimer {
private final PriorityQueue<MyTimerTask> priorityQueue = new PriorityQueue<>();
private Object lock = new Object();
public void schedule(Runnable runnable, long time) {
synchronized (lock) {
priorityQueue.add(new MyTimerTask(runnable, time));
lock.notify();
}
}
public MyTimer() {
Thread t = new Thread(()->{
while(true) {
synchronized (lock) {
if (priorityQueue.isEmpty()) {
//如果堆为空就阻塞等待
try {
lock.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
MyTimerTask myTimerTask = priorityQueue.peek();
if (myTimerTask.time <= System.currentTimeMillis()) {
//执行当前任务
myTimerTask.runnable.run();
priorityQueue.poll();
}else {
//时间没到就阻塞等待
try {
lock.wait(myTimerTask.time-System.currentTimeMillis());
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
};
}
});
t.start();
}
}
简单测试
public static void main(String[] args) {
MyTimer timer = new MyTimer();
//在3秒后打印3000
timer.schedule(()->{
System.out.println("3000");
}, 3000);
//在2秒后打印2000
timer.schedule(()->{
System.out.println("2000");
}, 2000);
//在1秒后打印1000
timer.schedule(()->{
System.out.println("1000");
}, 1000);
System.out.println("OK");
}
完整代码
import java.util.PriorityQueue;
class MyTimerTask implements Comparable<MyTimerTask>{
public Runnable runnable;
//存储绝对时间,后期直接和当前时间比较大小就行
public long time;
public MyTimerTask(Runnable runnable, long time){
this.runnable = runnable;
this.time = time + System.currentTimeMillis();
}
@Override
public int compareTo(MyTimerTask o) {
return (int) (this.time-o.time);
}
}
public class MyTimer {
private final PriorityQueue<MyTimerTask> priorityQueue = new PriorityQueue<>();
private Object lock = new Object();
public void schedule(Runnable runnable, long time) {
synchronized (lock) {
priorityQueue.add(new MyTimerTask(runnable, time));
lock.notify();
}
}
public MyTimer() {
Thread t = new Thread(()->{
while(true) {
synchronized (lock) {
if (priorityQueue.isEmpty()) {
//如果堆为空就阻塞等待
try {
lock.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
MyTimerTask myTimerTask = priorityQueue.peek();
if (myTimerTask.time <= System.currentTimeMillis()) {
//执行当前任务
myTimerTask.runnable.run();
priorityQueue.poll();
}else {
//时间没到就阻塞等待
try {
lock.wait(myTimerTask.time-System.currentTimeMillis());
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
};
}
});
t.start();
}
}