1 定时器简介
定时器是多线程编码中的一个重要组件,它就好比一个闹钟,例如我们想去坐车,但是不想现在去坐车,想8:30去坐车,于是我们订了一个8点钟的闹钟,也就是说定时器有一些逻辑,但并不想立即执行,而是要等一定的时间之后再来执行.
使用场景:浏览器访问服务器的时候就会使用定时器来判定是否超时, 标准库中其实已经提供了阻塞队列,定时器等基本组件,实际工作中我们直接使用标准库的就可以,下面的代码主要是为了理解其原理,加深对多线程的理解.
2 构成
- 使用一个Task类来描述"一段逻辑",也可以说一个要执行的任务,同时也要记住这个任务在什么时候开始执行;
- 使用一个阻塞优先队列来组织若干个Task;
这个阻塞优先队列既支持阻塞的特性,又支持按优先级的"先进先出" ,使用优先级队列的目的是为了保证队首元素就是那个最早要被执行的任务,本质上就是一个堆.判断当前队列中是否有任务时间到了,只需要判断队首元素是否到时间即可.
定时器四部分:
a) Task类来描述任务
Runnable 中有一个 run 方法,就可以借助这个 run 方法来描述要执行的具体的任务是啥, time 表示什么时候开始执行 command
,是一个绝对时间(ms级别的时间戳).
b) 阻塞优先队列组织这些任务
c) 扫描线程,定时扫描队首元素
d) 接口,让调用者能安排任务给定时器
代码截止到这里还有一个比较严重的问题,就是在扫描线程的时候,可能会出现忙等的现象,就例如我们订了一个8:30的闹钟,然后再八点的时候就开始每隔1秒钟就看一下时间,这就是所谓的"忙等"现象.
解决方式:wait,notify方法,wait(time)会有一个时间上限,代码阻塞到wait处,避免了频繁占用CPU,解决了忙等的问题,如下所示.
3 执行流程图
4 源码
package ThreadDemo;
import java.util.concurrent.PriorityBlockingQueue;
/**
* Created with IntelliJ IDEA
* Description:
* 定时器
* @author :Lee
* @description:TODO
* @date :Created in 2022/2/21 13:13
*/
public class Time {
static class Task implements Comparable<Task> {
private Runnable command;
private long time;
public Task(Runnable command, long after) {
this.command = command;
this.time = System.currentTimeMillis() + after;
}
public void run() {
command.run();
}
@Override
public int compareTo(Task o) {
return (int) (this.time - o.time);
}
}
static class Worker extends Thread {
private Object mailBox = null;
private PriorityBlockingQueue<Task> queue = null;
public Worker (PriorityBlockingQueue<Task> queue, Object mailBox) {
this.queue = queue;
this.mailBox = mailBox;
}
@Override
public void run() {
while (true) {
try {
Task task = queue.take();
long curTime = System.currentTimeMillis();
if(task.time > curTime) {
queue.put(task);
synchronized (mailBox) {
mailBox.wait(task.time - curTime);
}
} else {
task.run();
}
} catch (InterruptedException e) {
e.printStackTrace();
break;
}
}
}
}
static class Timer {
private Object mailBox = new Object();
private PriorityBlockingQueue<Task> queue = new PriorityBlockingQueue<>();
public Timer() {
Worker worker = new Worker(queue,mailBox);
worker.start();
}
public void schedule (Runnable command, long after) {
Task task = new Task(command,after);
queue.put(task);
synchronized (mailBox) {
mailBox.notify();
}
}
}
public static void main(String[] args) {
Timer timer = new Timer();
timer.schedule(new Runnable() {
@Override
public void run() {
System.out.println("上岸!");
timer.schedule(this,2000);
}
},2000);
}
}