文章目录
- 一. 定时器
- 二. 标准库中的定时器
- 三. 实现定时器
- 1. 创建MyTimerTask类
- 2. 通过一定的数据结构, 保存多个任务
- 3.MyTimer类的构造方法
- 4. 实现schedule方法
- 完整代码:
一. 定时器
定时器, 就是"闹钟"的效果
指定一个任务(Runnable), 并且指定一个时间, 此时这个任务不会立即执行, 而是在时间到达后, 再去执行
在日常开发中, 定时器是一个非常重要的基础组件, 甚至会把定时器功能单独封装成服务器, 供整个分布式系统来使用
二. 标准库中的定时器
标准库中提供了一个 Timer 类. Timer 类的核心方法为 schedule .
schedule 包含两个参数. 第一个参数指定即将要执行的任务代码, TimerTask类型(可以当成Runnable使用), 第二个参数delay指定多长时间之后执行 (单位为毫秒)
public static void main(String[] args) {
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("hello 3000");
}
},3000);
//可以设多个定时器
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("hello 2000");
}
},2000);
}
Timer内部包含了线程, 把线程创建封装起来了, 并且是前台线程, 阻止进程的结束
三. 实现定时器
1. 创建MyTimerTask类
需要包括执行的任务, 和执行任务的绝对时间(时间戳)
为什么用绝对时间,而不是用delay
为了方便后续执行任务的时候, 可以方便判定, 该任务是否能够执行
如果是delay, 随着时间的推移, 还需要更新delay, 比较麻烦
class MyTimerTask{
private Runnable runnable;
private long time;
public MyTimerTask(Runnable runnable, long delay){
this.runnable = runnable;
this.time = System.currentTimeMillis() + delay;
}
public void run(){
runnable.run();
}
public long getTime(){
return this.time;
}
}
2. 通过一定的数据结构, 保存多个任务
如果我们使用ArrayList
因为我们要保证, delay时间短的先执行, 那么我们就需要不停地循环扫描ArrayList这里的每个任务, 判定是否要执行
更好的办法就是使用优先级队列
把这些任务通过优先级队列保存起来, 按照时间作为优先级的先后标准, 就可以做到, 队首元素就是时间最靠前的任务, 那么执行任务的时候, 只需要取队首元素即可
那么, 使用优先级队列, 就需要里面的对象实现Comparable接口, 那么MyTimerTask就需要修改一下:
class MyTimerTask implements Comparable<MyTimerTask>{
private Runnable runnable;
private long time;
public MyTimerTask(Runnable runnable, long delay){
this.runnable = runnable;
this.time = System.currentTimeMillis() + delay;
}
public void run(){
runnable.run();
}
public long getTime(){
return this.time;
}
@Override
public int compareTo(MyTimerTask o) {
return (int)(this.getTime() - o.getTime());
//return (int)(o.getTime() - this.getTime());
}
}
因为我们要实现的是小根堆, 在compareTo方法中, 是 谁 - 谁, 我们不要去背, 随便写一个试试就知道对不对了, 背很容易背错!!!
3.MyTimer类的构造方法
在MyTimer类的构造方法中, 创建一个线程, 用这个线程来执行队列中的任务
public MyTimer(MyTimerTask myTimerTask, long delay){
Thread thread = new Thread(() -> {
while(true){
//如果任务队列为空,就重新判断是否要执行
if(queue.size() == 0){
continue;
}
MyTimerTask task = queue.peek();
//如果此时的时间 >= 要执行的任务的时间, 则执行任务, 并出队列, 反之则重新判断
if(task.getTime() >= System.currentTimeMillis()){
task.run();
queue.poll();
}else{
continue;
}
}
});
thread.start();
}
改进:
- 上述代码中, 如果任务队列为空或者还没到执行的时间, 就会处于忙等的状态, 短时间内循环很多次, 直到队列不为空或等到执行时间, 会一直消耗cpu资源, 但是并没有执行真正的任务, 所以我们要让线程处于等待的时候, 能够释放cpu
所以可以使用 wait - PriorityQueue本身是线程不安全的, 所以要加上锁
为什么不用PriorityBlockingQueue?
因为PriorityBlockingQueue只能处理队列为空的时候阻塞, 不能处理时间没到的阻塞
如果使用PriorityBlockingQueue, 还是需要再加一把锁, 此时代码就更加复杂了引入了两把锁, 稍有不慎, 就容易引起死锁!
public MyTimer(MyTimerTask myTimerTask, long delay){
Object locker = new Object();//创建锁对象
Thread thread = new Thread(() -> {
try{
while(true){
synchronized (locker){
//如果任务队列为空,就阻塞等待
if(queue.size() == 0){
locker.wait();
}
MyTimerTask task = queue.peek();
//如果此时的时间 >= 要执行的任务的时间, 则执行任务, 并出队列,
// 反之则阻塞等待task.getTime() - curTime这些时间, 时间到了就执行任务
long curTime = System.currentTimeMillis();
if(task.getTime() >= curTime){
task.run();
queue.poll();
}else{
locker.wait(task.getTime() - curTime);
}
}
}
}catch(InterruptedException e){
e.printStackTrace();
}
});
thread.start();
}
那么上述等待为什么不用sleep?
- sleep睡着了就是真的睡着了, 不能像wait一样可以唤醒
此时, 如果来了一个比队首元素执行时间还要进的元素, 那么我们就需要唤醒等待, 并且重新安排等待时间, 这时sleep做不到的- 最重要的一点, 就是sleep等待期间不会释放锁, wait会释放锁
如果不释放锁, 怎么进行入队操作呢?
4. 实现schedule方法
这个方法主要是为了添加任务, 并创建线程, 利用线程完成任务
public void schedule(Runnable runnable, long delay){
synchronized (locker){
MyTimerTask myTimerTask = new MyTimerTask(runnable, delay);
queue.offer(myTimerTask);
//每次添加新的任务, 都要唤醒wait, 重新判断队首元素是否要执行
locker.notify();
}
}
完整代码:
class MyTimerTask implements Comparable<MyTimerTask>{
private Runnable runnable;
private long time;
public MyTimerTask(Runnable runnable, long delay){
this.runnable = runnable;
this.time = System.currentTimeMillis() + delay;
}
public void run(){
runnable.run();
}
public long getTime(){
return this.time;
}
@Override
public int compareTo(MyTimerTask o) {
return (int)(this.time - o.time);
//return (int)(o.time - this.time);
}
}
class MyTimer{
private PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();
private Object locker = new Object();//创建锁对象
public MyTimer(){
Thread thread = new Thread(() -> {
try{
while(true){
synchronized (locker){
//如果任务队列为空,就阻塞等待
if(queue.size() == 0){
locker.wait();
}
MyTimerTask task = queue.peek();
//如果此时的时间 >= 要执行的任务的时间, 则执行任务, 并出队列,
// 反之则阻塞等待task.getTime() - curTime这些时间, 时间到了就执行任务
long curTime = System.currentTimeMillis();
if(task.getTime() <= curTime){
task.run();
queue.poll();
}else{
locker.wait(task.getTime() - curTime);
}
}
}
}catch(InterruptedException e){
e.printStackTrace();
}
});
thread.start();
}
public void schedule(Runnable runnable, long delay){
synchronized (locker){
MyTimerTask myTimerTask = new MyTimerTask(runnable, delay);
queue.offer(myTimerTask);
locker.notify();
}
}
}
public class Demo30 {
public static void main(String[] args) {
MyTimer timer = new MyTimer();
timer.schedule(new Runnable() {
@Override
public void run() {
System.out.println("hello 3000");
}
},3000);
//可以设多个定时器
timer.schedule(new Runnable() {
@Override
public void run() {
System.out.println("hello 2000");
}
},2000);
}
}
执行结果: