"定时器"是实际开发中非常常用的组件,比如在Map中,使其中的某个key在规定时间被删除,类似的..
标准库中的定时器
- 在标准库中有一个Timer类, 核心方法为schedule.
- schedule中包含了两个参数,其一是将要执行的任务(TimerTask()),其二是指定多长时间后执行.
public static void main(String[] args) {
Timer timer = new Timer();
// 不是在调用schedule()的线程执行的,而是通过Timer内部的线程,来进行执行
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("定时器3");
}
}, 3000);
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("定时器2");
}
}, 2000);
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("定时器1 ");
}
}, 1000);
System.out.println("程序开始运行");
}
这里我写了三个任务,分别在1s,2s和3s后启动.
在Timer中,有自己的线程,为了保证随时可以处理新安排的任务,这个线程会持续执行.
它是前台线程.
定时器模拟实现
- 创建一个MyTimerTask类,包含任务的内容及时间,还有一些get,set方法.
- 使用优先级队列,把多个TimerTask给组织起来.
- 写一个扫描线程,监控队首元素的任务是否时间到,到了就调用run()方法.
class MyTimerTask {
// 时间
private long time;
// 任务
private Runnable runnable;
public MyTimerTask(Runnable runnable, long time) {
// 任务要启动时间为当前时间+你设置等待的时间
this.time = System.currentTimeMillis() + time;
this.runnable = runnable;
}
public long getTime() {
return time;
}
public Runnable getRunnable() {
return runnable;
}
}
class Timer {
private PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();
public void schedule(Runnable runnable, long time) {
MyTimerTask task = new MyTimerTask(runnable, time);
queue.offer(task);
}
// 扫描线程
public Timer() {
Thread thread = new Thread(() -> {
while (true) {
if(queue.isEmpty()) {
continue;
}
MyTimerTask task = queue.peek();
long curTime = System.currentTimeMillis();
if(curTime > task.getTime()) {
queue.poll();
task.getRunnable().run();
}else {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
});
thread.start();
}
}
我们完成了定时器的模拟实现,但是这里有3个问题,看到这里的小伙伴本博主给你竖个大拇指!
你们可以自行思考,看看上述代码有什么问题,再来看下面印象就会比较深刻.
下面是改进过的代码,及问题解析.
class MyTimerTask1 implements Comparable<MyTimerTask1> {
private long time;
private Runnable runnable;
public MyTimerTask1(Runnable runnable, long time) {
this.time = System.currentTimeMillis()+time;
this.runnable = runnable;
}
public long getTime() {
return time;
}
public Runnable getRunnable() {
return runnable;
}
// 因为按照时间从小到大,应该建立小堆
@Override
public int compareTo(MyTimerTask1 o) {
return (int)(this.time - o.time);
}
}
class Timer1 {
private PriorityQueue<MyTimerTask1> queue = new PriorityQueue<>();
public void schedule(Runnable runnable, long time) {
// 添加任务
synchronized (this) {
MyTimerTask1 task = new MyTimerTask1(runnable, time);
queue.offer(task);
this.notify();
}
}
public Timer1() {
Thread thread = new Thread(() -> {
while (true) {
synchronized (this) {
// 为空时,不应该继续循环,而应该等待添加,直到有任务添加为止
while (queue.isEmpty()) {
try {
this.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
MyTimerTask1 task = queue.peek();
long curTime = System.currentTimeMillis();
if(curTime > task.getTime()) {
queue.poll();
task.getRunnable().run();
}else {
try {
//Thread.sleep(1000);
// 到执行任务时间相差的时间
this.wait(task.getTime() - curTime);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
});
thread.start();
}
}
public class Demo24 {
public static void main(String[] args) {
Timer1 timer = new Timer1();
timer.schedule(new Runnable() {
@Override
public void run() {
System.out.println("第三个任务");
}
}, 3000);
timer.schedule(new Runnable() {
@Override
public void run() {
System.out.println("第二个任务");
}
}, 2000);
timer.schedule(new Runnable() {
@Override
public void run() {
System.out.println("第一个任务");
}
}, 1000);
System.out.println("计时器启动!");
}
}
- 线程安全: PriorityQueue<>这个集合类,不是线程安全的,会在主线程和扫描线程同时使用,
所以针对queue的操作都要进行加锁. - 使用sleep来进行休眠是不合适的,sleep进入阻塞后,不会释放锁,会影响其它线程,来执行schedule.使用wait加notify的方式来进行休眠唤醒.
- 我们要求放到优先级队列中的元素是"可比较"的,可以通过使用Comparable或者Comparator定义任务之间的比较规则,此处我用的是Comparable来实现优先级队列元素比较的.
结语
好了,定时器的知识就总结到这里,有收获的小伙伴点个赞,收藏一下吧,博主更新不易…