🍁 一、定时器概论
1.什么是定时器
定时器是软件开发中的一个重要组件。类似于一个 “闹钟”,达到一个设定的时间之后, 就执行某个指定好的代码
- 访问某个网站出现了网络卡顿,浏览器就会转圈圈(阻塞等待),这个等待不是无限的等待,到达一定时间以后,就显示超时访问
- 在前端开发中网站上的动画效果,也是通过定时器实现的,比如每隔30ms,把页面往下滚动几个像素
2.Java标准库中的定时器——Timer
- 标准库中提供了一个 Timer 类
构造方法
构造方法 | 备注 | |
---|---|---|
1 | public Timer() | 无参, 定时器关联的线程为前台线程, 线程名为默认值. |
2 | public Timer(boolean isDaemon) | 指定定时器中关联的线程类型, true(后台线程), false(前台线程). |
3 | public Timer(String name) | 指定定时器关联的线程名, 线程类型为前台线程 |
4 | public Timer(String name, boolean isDaemon) | 指定定时器关联的线程名和线程类型 |
如果没有指定定时器中关联的线程类型,默认为false(前台线程)
前台线程:前台线程会影响java进程的结束,只有在前台线程结束后,java线程才能结束。
- Timer 类的核心方法为
schedule
,schedule 方法是给Timer
注册一个任务, 这个任务在指定时间后进行执行,TimerTask
类就是专门描述定时器任务的一个抽象类, 它实现了Runnable
接口.
public abstract class TimerTask implements Runnable
3.代码演示定时器
public static void main(String[] args) {
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("1秒后执行");
}
}, 1000);
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("2秒后执行");
}
}, 2000);
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("3秒后执行");
}
}, 3000);
}
通过结果我们看到了定时器的作用,就是让一个任务在某一个时刻执行。
同时,我们发现这个代码没有运行结束,就是因为这是前台线程,会影响java进程的结束。
🍁 二、定时器的实现
定时器的构成:
- 队列中的每个元素是一个 Task 对象,Task中带有一个时间属性和一个Runnable任务属性
- 带有优先级的阻塞队列
为啥要带优先级呢?
因为阻塞队列中的任务都有各自的执行时刻 (delay)。最先执行的任务一定是 delay 最小的. 使用带优先级的队列就可以高效的把这个 delay 最小的任务找出来,使用带有阻塞功能的优先队列用以维护线程安全
- schedule方法,该方法用于往队列中插入元素
- 扫描线程不断去扫描队首元素,看看队首元素是不是已经到点了,如果到点就执行这个任务,如果没有到点,就把这个队首元素塞回队列中,继续扫描
// 表示一个任务.
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 {
// 阻塞队列, 只有阻塞的入队列和阻塞的出队列, 没有阻塞的查看队首元素.
MyTask myTask = queue.take();
long curTime = System.currentTimeMillis();
if (myTask.time <= curTime) {
// 时间到了, 可以执行任务了
myTask.runnable.run();
} else {
// 时间还没到
// 把刚才取出的任务, 重新塞回队列中.
queue.put(myTask);
synchronized (locker) {
locker.wait(myTask.time - curTime);
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
}
}
测试:
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);
}