一、定时器是什么?
定时器类似于我们生活中的闹钟,可以设定一个时间来提醒我们。
而定时器是指定一个时间去执行一个任务,让程序去代替人工准时操作。
标准库中的定时器:
方法 | 作用 |
void schedule(TimerTask task, long delay) | 指定delay时间之后(单位毫秒)执行任务task |
public static void main(String[] args) {
Timer timer=new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("该起床了");
System.out.println("三秒后刷牙洗脸");
}
},3000);
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("起床后刷牙洗脸");
}
},6000);
}
刚开始等待三秒
执行完第一个任务三秒后执行第二个任务
这段程序就是创建一个定时器,然后提交一个3000ms后执行的任务。
二、自定义定时器
我们自己实现一个定时器的前提是我们需要弄清楚定时器都有什么:
1.一个扫描线程,负责来判断任务是否到时间需要执行
2.需要有一个数据结构来保存我们定时器中提交的任务
创建一个扫描线程相对比较简单,我们需要确定一个数据结构来保存我们提交的任务,我们提交过来的任务,是由任务和时间组成的,我们需要构建一个Task对象,数据结构我们这里使用优先级队列,因为我们的任务是有时间顺序的,具有一个优先级,并且要保证在多线程下是安全的,所以我们这里使用:PriorityBlockingQueue比较合适。
首先我们构造一个Task对象
class MyTask implements Comparable<MyTask> {
// 任务
private Runnable runnable;
// 任务执行的时间
private long time;
public MyTask(Runnable runnable, long delay) {
// 校验任务不能为空
if (runnable == null) {
throw new IllegalArgumentException("任务不能为空.");
}
// 时间不能为负数
if (delay < 0) {
throw new IllegalArgumentException("执行时间不能小于0.");
}
this.runnable = runnable;
// 计算出任务执行的具体时间
this.time = delay + System.currentTimeMillis();
}
public Runnable getRunnable() {
return runnable;
}
public long getTime() {
return time;
}
@Override
public int compareTo(MyTask o) {
if (this.getTime() > o.getTime()) {
return 1;
} else if (this.getTime() < o.getTime()) {
return -1;
} else {
return 0;
}
// return (int) (this.time - o.getTime());
}
}
这里有定时时间和系统当前的时间进行比较,所以使用后Comparable<MyTask>接口
MyTimer类:
public MyTimer() {
// 创建扫描线程
Thread thread = new Thread(() -> {
// 不断地扫描队列中的任务
while (true) {
try {
// System.out.println("实例化出来的对象 "+ this);
// 1. 从队列中取出任务
MyTask task = this.queue.take();
// 2. 判断到没到执行时间
long currentTime = System.currentTimeMillis();
if (currentTime >= task.getTime()) {
// 时间到了,执行任务
task.getRunnable().run();
} else {
// 当前时间与任务执行时间的差
long waitTime = task.getTime() - currentTime;
// 没有到时间,重新放回队列
queue.put(task);
synchronized (locker) {
// 加入等时间
locker.wait(waitTime);
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "scanThread");
// 启动线程,真正去系统中申请资源
thread.start();
// 创建一个后台线程
Thread daemonThread = new Thread(() -> {
while (true) {
synchronized (locker){
locker.notifyAll();
}
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
daemonThread.setDaemon(true);
daemonThread.start();
}
假设我们早上8点定时12点的任务,当阻塞队列添加任务时,如果再添加一个任务,这个任务的执行时间在阻塞对列添加之前任务时间之前,可能导致错过当前任务的执行时间,所以设置一个后台线程时刻唤醒Mytask线程避免出现线程安全问题!!