专栏简介: JavaEE从入门到进阶
题目来源: leetcode,牛客,剑指offer.
创作目标: 记录学习JavaEE学习历程
希望在提升自己的同时,帮助他人,,与大家一起共同进步,互相成长.
学历代表过去,能力代表现在,学习能力代表未来!
1.定时器的概念
2.标准库中的定时器
3.实现定时器
3.1定时期的构成:
3.2 实现步骤:
定时器类似于一个"闹钟" , 是软件开发中的一个重要组件 , 达到一个设定时间后就会执行某段代码.
例如网络通信中 , 如果对方500ms都没有返回数据 , 那么就执行定时器中的任务:断开重连.
例如实现一个Map , 希望 Map 中的 key 在3s后过期(自动删除).
...............................
类似于以上的场景就需要用到定时器.
public static void main(String[] args) {
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("hello");
}
},500);
}
为什么要带优先级? 如果优先级队列按待执行时间(delay)来排序的话 , 每次只需取出队首的元素就可高效的把delay最小的任务给找出来.
class MyTimer{
public void schedule(Runnable runnable, long after){
//具体执行的任务
}
}
class MyTask implements Comparable{
private Runnable runnable;
private long time;
public MyTask(Runnable runnable, long time) {
this.runnable = runnable;
this.time = time;
}
//获取当前任务的时间
public long getTime(){
return time;
}
//执行任务
public void run(){
runnable.run();
}
@Override
public int compareTo(MyTask1 o) {
return (int) (this.time-o.time);
}
}
class MyTimer{
PriorityBlockingQueue queue = new PriorityBlockingQueue<>();
public void schedule(Runnable runnable , long after){
MyTask1 myTask = new MyTask(runnable , System.currentTimeMillis()+after);
queue.put(myTask);
}
}
class MyTimer1 {
Thread scan = null;
PriorityBlockingQueue queue = new PriorityBlockingQueue<>();
public void schedule(Runnable runnable, long after) {
MyTask1 myTask1 = new MyTask1(runnable, System.currentTimeMillis() + after);
queue.put(myTask1);
}
public MyTimer1() {
scan = new Thread(() -> {
while (true) {
try {
MyTask1 myTask1 = queue.take();
if (System.currentTimeMillis() < myTask1.getTime()) {
//时间还没到把任务塞回去
queue.put(myTask1);
} else {
//时间到了执行任务
myTask1.run();
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
scan.start();
}
}
但当前代码存在一个很严重的问题 , 就是while(true)循环速度太快 , 造成了无意义的 CPU 浪费.
public void schedule(Runnable runnable, long after) {
MyTask1 myTask1 = new MyTask1(runnable, System.currentTimeMillis() + after);
queue.put(myTask1);
//一但加入新的元素就唤醒 wait ,重新判断
synchronized (this) {
this.notify();
}
}
public MyTimer1() {
scan = new Thread(() -> {
while (true) {
try {
MyTask1 myTask1 = queue.take();
if (System.currentTimeMillis() < myTask1.getTime()) {
//时间还没到把任务塞回去
queue.put(myTask1);
synchronized (this) {
this.wait(myTask1.getTime()-System.currentTimeMillis());
}
} else {
//时间到了执行任务
myTask1.run();
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
scan.start();
}
此时代码还存在一点瑕疵 , 假设当前时间是 13:00 如果队首的任务执行时间是 14:00 , 这时当代码执行到 queue.put(myTask1);时该线程被 CPU 调度走 , 与此同时另一个线程调用 schedule 方法 , 注册一个 13:30 执行的任务 , 将其放入队首并通知 wait.但此时 notify 方法空打一炮 , 等到扫描线程被调度回来时 , wait 还是要等待1h , 这样机会导致 13:30 的任务错过其执行时间.
产生上述问题的根本原因是 , take() 和 wait 操作不是原子的 , 如果在 take() 和 wait 之间加上锁 , 保证这个执行过程中不会有新的任务进来 , 问题自然解决.
public MyTimer1() {
scan = new Thread(() -> {
while (true) {
synchronized (this) {
try {
MyTask1 myTask1 = queue.take();
if (System.currentTimeMillis() < myTask1.getTime()) {
//时间还没到把任务塞回去
queue.put(myTask1);
this.wait(myTask1.getTime()-System.currentTimeMillis());
} else {
//时间到了执行任务
myTask1.run();
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
});
scan.start();
}
完整代码如下:
class MyTask1 implements Comparable{
private Runnable runnable;
private long time;
public MyTask1(Runnable runnable, long time) {
this.runnable = runnable;
this.time = time;
}
//获取当前任务的时间
public long getTime(){
return time;
}
//执行任务
public void run(){
runnable.run();
}
@Override
public int compareTo(MyTask1 o) {
return (int) (this.time-o.time);
}
}
/**
* 定时器类
*/
class MyTimer1 {
Thread scan = null;
PriorityBlockingQueue queue = new PriorityBlockingQueue<>();
public void schedule(Runnable runnable, long after) {
MyTask1 myTask1 = new MyTask1(runnable, System.currentTimeMillis() + after);
queue.put(myTask1);
//一但加入新的元素就唤醒 wait ,重新判断
synchronized (this) {
this.notify();
}
}
public MyTimer1() {
scan = new Thread(() -> {
while (true) {
synchronized (this) {
try {
MyTask1 myTask1 = queue.take();
if (System.currentTimeMillis() < myTask1.getTime()) {
//时间还没到把任务塞回去
queue.put(myTask1);
this.wait(myTask1.getTime()-System.currentTimeMillis());
} else {
//时间到了执行任务
myTask1.run();
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
});
scan.start();
}
}
public class ThreadDemo8 {
public static void main(String[] args) {
MyTimer1 myTimer1 = new MyTimer1();
myTimer1.schedule(new Runnable() {
@Override
public void run() {
System.out.println("hello,1");
}
},300);
myTimer1.schedule(new Runnable() {
@Override
public void run() {
System.out.println("hello,2");
}
},600);
}
}