前引
定时器是什么?
定时器也是软件开发中的⼀个重要组件. 类似于⼀个 "闹钟". 达到⼀个设定的时间之后, 就执⾏某个指定。
1.思路
a)先使用一下Timer中schedule方法,了解其功能,这个功能也就是你自己实现时的需求。
public static void main(String[] args) {
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println(3000);
}
},3000);
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println(2000);
}
},2000);
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println(1000);
}
},1000);
}
//打印结果:
//1000
//2000
//3000
通过分析得到大致需求:
1.有一个类似TimerTask的类,说明你要执行的任务和多长时间后执行
class MyTimerTask{
private Runnable task;
private long time;
public MyTimerTask(Runnable task,long time){
this.task = task;
this.time = System.currentTimeMillis() + time;
}
}
2.有一个类似Timer的类,类中有schedule方法;执行任务。
class MyTimer{
private PriorityQueue<MyTimerTask> tasks = new PriorityQueue<>();
public void schedule(Runnable task, long time){
MyTimerTask timerTask = new MyTimerTask(task,time);
addTask(timerTask);
}
//执行任务
public MyTimer(){
Thread t = new Thread(() -> {
while (true) {
//执行。。。。
});
t.start();
}
//添加任务
public void addTask(MyTimerTask task){
tasks.add(task);
}
}
3.还要有一数据结构来管理要执行的任务
任务的特点:
1.按照多久后执行的时间从小到大排序
2.会经常插入和删除
这里我选择的是优先级队列 PriorityQueue(从插入算法的时间复杂度分析,查找过程:,插入过程:)这些是顺序表,链表等达不到的。
private PriorityQueue<MyTimerTask> tasks = new PriorityQueue<>();
2.完整代码
注意:
1.线程安全问题。
2.重写Comparable中compareTo方法(原因:PriorityQueue 中存放 MyTimer 类不能直接比较)。
3.当优先级队列中为空时,使用 wait() 等待,把调用的资源还给系统,当新加入任务时notify()唤醒。
4.当等待时间最小的任务未达到执行时间时,进行 wait() 等待一定时间,把调用的资源还给系统。
其中:3和4是为了提高对系统资源充分利用,不然就会出现如下代码:
Thread t = new Thread(() -> {
while (true) {
synchronized (locker) {
if (tasks.isEmpty()) {
MyTimerTask task = tasks.peek();
if (task.getTime() > System.currentTimeMillis()) {
continue;
} else {
tasks.poll().getTask().run();
}
}
}
}
});
bug: 当优先级队列为空时或未达到执行时间,上述代码会无休止的进行无意义的判断,浪费大量系统资源。
class MyTimer{
private PriorityQueue<MyTimerTask> tasks = new PriorityQueue<>();
private Object locker = new Object();
//执行任务
public MyTimer() {
Thread t = new Thread(() -> {
while (true) {
synchronized (locker) {
//问题1:这里为什么使用while而不是if?
while (tasks.isEmpty()) {
try {
locker.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
MyTimerTask task = tasks.peek();
if (task.getTime() > System.currentTimeMillis()) {
try {
//问题2:为什么使用wait 而不使用sleep?
locker.wait(task.getTime() - System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
tasks.poll().getTask().run();
}
}
}
});
t.start();
}
public void schedule(Runnable task, long time){
MyTimerTask timerTask = new MyTimerTask(task,time);
synchronized (locker) {
addTask(timerTask);
locker.notify();
}
}
//添加任务
public void addTask(MyTimerTask task){
tasks.add(task);
}
}
解答问题:
问题1:上述代码为什么使用while而不是if?
在你单纯实现定时器时并不会出现什么问题,但在一个项目中有其他的notify不经意间唤醒了怎么办?程序不就报错了吗?使用while,代码执行过程中不就更加安全。
问题2:上述代码为什么使用wait 而不使用sleep?
sleep()方法,不会把资源还给系统,只是死等;当加入一个可以执行的任务时,仍然要在sleep完后才能执行。
wait()方法则会把资源还给系统;当加入一个可以执行的任务时,可以通过notify()唤醒进行任务的执行。
//测试代码
public class Test {
public static void main(String[] args) throws InterruptedException {
MyTimer myTimer = new MyTimer();
myTimer.schedule(new Runnable() {
@Override
public void run() {
System.out.println(3000);
}
},3000);
myTimer.schedule(new Runnable() {
@Override
public void run() {
System.out.println(2000);
}
},2000);
myTimer.schedule(new Runnable() {
@Override
public void run() {
System.out.println(1000);
}
},1000);
}
}
最后:如果你有想法可以与自己项目相结合,进行使用!