1. 什么是定时器
所谓的定时器,可以理解为闹钟,当指定时间到,就会执行一个代码
定时器是一种实际开发中常用的组件
比如你打开一个网页,如果在多少时间没显示,就会断开连接,请求重试。
在Java标准库中就提供了定时器
- 标准库中提供了一个 Timer 类,该类的核心方法是 schedule
- schedule 中有两个参数,第一个参数是需要执行的任务是什么,第二个参数是执行多长时间后执行(单位是毫秒)
public class Demo25 {
public static void main(String[] args) {
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("hello timer");
}
},3000);
System.out.println("hello main");
}
}
可以看出进程并没有结束,这是因为 Timer 里内置了前台线程,会阻止进程的结束~
2. 自己实现一个定时器
/**
* 优先级阻塞队列
*/
// 表示一个任务
class MyTask implements Comparable<MyTask> {
public Runnable runnable;
public long time;
public MyTask(Runnable runnable, long delay) {
this.runnable = runnable;
// 取当前时刻的时间戳 + delay ,作为该任务实际执行的时间戳
this.time = System.currentTimeMillis() + delay;
}
// 意味着每次取出的是时间的最小元素
@Override
public int compareTo(MyTask o) {
return (int) (this.time - o.time);
}
}
class MyTimer {
// 这个结构,带有优先级的阻塞队列,核心数据结构
private PriorityBlockingQueue<MyTask> queue = new PriorityBlockingQueue<>();
private Object locker = new Object();
// 此处的 delay 是一个形如 3000 这样的数字(多长时间之后,执行该任务)
public void schedule(Runnable runnable, long delay) {
// 根据参数,构造 MyTask,插入列队即可
MyTask myTask = new MyTask(runnable, delay);
queue.put(myTask);
synchronized (locker) {
locker.notify();
}
}
// 在这里构造线程,负责执行具体任务
public MyTimer() {
Thread t = new Thread(() -> {
while (true) {
try {
// 阻塞队列,只有阻塞的入队列和阻塞的出队列,没有阻塞的查看队首元素
synchronized (locker) {
MyTask myTask = queue.take();
long curTime = System.currentTimeMillis(); // 当前时刻
if (myTask.time <= curTime) {
// 时间到了
myTask.runnable.run();
} else {
// 时间没到
// 把刚才取出的任务,重新塞回队列中
queue.put(myTask);
locker.wait(myTask.time - curTime); // 此处不加,会造成忙等,如果加sleep,可能会错过某个新的时间更早的任务
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
}
}
public class Demo19 {
public static void main(String[] args) {
MyTimer myTimer = new MyTimer();
myTimer.schedule(new Runnable() {
@Override
public void run() {
System.out.println("hello4");
}
}, 4000);
myTimer.schedule(new Runnable() {
@Override
public void run() {
System.out.println("hello3");
}
}, 3000);
myTimer.schedule(new Runnable() {
@Override
public void run() {
System.out.println("hello2");
}
}, 2000);
myTimer.schedule(new Runnable() {
@Override
public void run() {
System.out.println("hello1");
}
}, 1000);
System.out.println("hello0");
}
}
最关键的地方:
- 重写compareTo方法
- 使用带参数 wait 方法防止出现忙等状态