目录
一、定时器
定时器类似一个闹钟,经过指定的时间后执行要求的代码。
1. 标准库中的定时器
- Timer 类
- Timer 类 的 schedule 方法
通过 schedule 方法,传入要执行的任务(TimerTask类,专门用来描述任务) 以及 要执行的时间后,就实现了一个定时器。
具体使用如下:
public static void main(String[] args) {
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("hello");
}
},3000);
System.out.println("man");
}
2. 自己实现一个定时器
大致思路:
- 1. 描述一个任务: Runnable + time
- 2. 使用优先级队列来组织若干个任务:PriorityBlockingQueue
- 3. 实现 schedule 方法来将任务放入优先级队列中
- 4. 创建一个线程,用这个扫描线程不断获取队列的队首元素,并且判断时间是否到达
- 5. 注意:① 让 MyTimerTask 支持比较;② 解决线程的忙等问题
更加详细的解释:
- 1. 描述一个任务的类,要求存放要执行的具体任务(Runnable类)以及 多长时间后要执行(after):MyTimerTask。
- 2. 管理任务的数据结构,因为是定时器,所以要求要能获取到最短时间的任务 —— 优先级队列 (PriorityQueue) —— 因为涉及到阻塞 —— PriorityBlockingQueue。
- 3. 定时器的主要方法:schedule —— 将任务加入定时器。
- 4. 中定时器需要不断地获取当前时间,并且判断是否到了第一个要执行任务的时间 —— 在定时器中需要有一个额外的线程进行不断的循环判断。
大体框架写好后,还要注意:
- 1. 优先级队列中存放的是我们新建的任务类的对象,而在优先级队列中,需要比较每个任务的执行时间来进行排序 —— 在任务类中继承 Comparable 接口,实现 compareTo 方法
- 2. 在定时器中的线程中,不断高速循环判断队列中的第一个元素,当当前时间里目标时间很大时,在这段空闲时间中,这样不断访问判断会占用大量的 CPU 资源,属于忙等。因此,我们需要指定等待时间 —— wait(等待时间),先判断第一个队列中的任务,然后进行设置等待时间,接下来线程就进入阻塞状态,等时间到了才会继续进行访问
- 3. 在线程等待的过程中,可能会有其他任务加入到队列中,但如果新加入的任务的时间比队列中第一个任务的时间还要早时,我们就要先执行这个新加入的任务了。因此,考虑到这种情况,我们每加入一个元素,就要将线程唤醒一次,让线程继续判断这个新加入的任务 —— notify(),当线程没有在阻塞中也无所谓
具体代码:
class MyTimerTask implements Comparable<MyTimerTask> {
private Runnable runnable = null;
private long time = 0;
public MyTimerTask(Runnable runnable, int after) {
this.runnable = runnable;
this.time = after + System.currentTimeMillis();
}
public long getTime() {
return time;
}
public void run() {
runnable.run();
}
public int compareTo(MyTimerTask o) {
//要让时间大的在后,时间小的在前(升序)
return (int) (this.time - o.time);
}
}
//定时器
class MyTimer {
private Object locker = new Object();
private PriorityBlockingQueue<MyTimerTask> queue = new PriorityBlockingQueue<>();
public void schedule(Runnable runnable, int after) {
MyTimerTask task = new MyTimerTask(runnable, after);
queue.put(task);
synchronized (locker) {
locker.notify();
}
}
//构造函数中,创建一个线程,不断循环任务队列,看时间是否到了
public MyTimer() {
Thread thread = new Thread(() -> {
while(true) {
try {
MyTimerTask task2 = queue.take();
long curtime = System.currentTimeMillis();
if (curtime < task2.getTime()) {
queue.put(task2);
synchronized (locker) {
locker.wait(task2.getTime() - curtime);
}
} else {
//时间到了,执行 run
task2.run();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread.start();
}
}
public class Test {
//测试
public static void main(String[] args) {
MyTimer timer = new MyTimer();
timer.schedule(new Runnable() {
@Override
public void run() {
System.out.println("hello");
}
}, 3000);
//用main 线程进行对比
System.out.println("main");
}
}