> 文章列表 > JavaEE——多线程定时器(Timer)

JavaEE——多线程定时器(Timer)

JavaEE——多线程定时器(Timer)

点一点了解更多

 定时器是什么?定时器能做什么?本篇文章带你深入了解并实现


目录

一、定时器

 1.1java标准库定时器

1.2定时器代码

二、模拟定时器实现

2.1先创建个任务类,表示执行的任务是啥,任务啥时候执行?

2.2创建定时器类

三、完整代码及注意事项


一、定时器

定时器是什么?

定时器也是软件开发中的一个重要组件. 类似于一个 "闹钟". 达到一个设定的时间之后, 就执行某个指定好的代码
定时器是一种实际开发中非常常用的组件,比如网络通信中, 如果对方 500ms 内没有返回数据, 则断开连接尝试重连,比如一个 Map, 希望里面的某个 key 在 3s 之后过期,类似于这样的场景就需要用到定时器

 1.1java标准库定时器

标准库提供的定时器:Timer在java.util这个集合类中

 

1.2定时器代码

标准库中提供了一个 Timer 类,Timer 类的核心方法为 schedule
schedule 包含两个参数,第一个参数指定即将要执行的任务代码, 第二个参数指定多长时间之后执行 (单位为毫秒)

安排一个工作,这个工作不是立即完成的,而是未来某个时间点~~

public class ThreadDemo6 {public static void main(String[] args) {Timer timer = new Timer();timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("hello");}},4000);timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("hello");}},3000);timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("hello");}},2000);timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("hello");}},1000);}
}

  

 可见线程还没有结束,那这是因为什么呢?

是因为Timer里面内置了线程,(还是前台线程)会阻止线程结束

二、模拟定时器实现

定时器,内部管理的不仅仅是一个任务,可以管理很多任务的!!

虽然任务有很多,但是他们的触发时间是不同的,每次都找到这些任务中,最先到达的任务执行;一个线程先执行最早的任务,做完了之后再执行第二早的,那么应该用什么去存储这些任务呢?

当然是堆!!!java标准库中提供了带优先级的阻塞队列

  • 队列中的每个元素是一个 Task 对象
  • Task 中带有一个时间属性, 队首元素就是即将要执行的任务
  • 同时有一个 worker 线程一直扫描队首元素, 看队首元素是否需要执行

2.1先创建个任务类,表示执行的任务是啥,任务啥时候执行?

// 表示一个任务.
class MyTask implements Comparable<MyTask>{public Runnable runnable;// 为了方便后续判定, 使用绝对的时间戳.public long time;public MyTask(Runnable runnable, long delay) {this.runnable = runnable;// 取当前时刻的时间戳 + delay, 作为该任务实际执行的时间戳this.time = System.currentTimeMillis() + delay;}@Overridepublic int compareTo(MyTask o) {return (int) (this.time - o.time);}
}

2.2创建定时器类

class MyTimer{// 这个结构, 带有优先级的阻塞队列. 核心数据结构private PriorityBlockingQueue<MyTask> queue = new PriorityBlockingQueue<>();// 创建一个锁对象private Object locker = new Object();// 此处的 delay 是一个形如 3000 这样的数字 (多长时间之后, 执行该任务)public void schedule(Runnable runnable,long delay){// 根据参数, 构造 MyTask, 插入队列即可.MyTask myTask = new MyTask(runnable,delay);queue.put(myTask);synchronized (locker){locker.notify();}}// 在这里构造线程, 负责执行具体任务了.public MyTimer(){Thread t = new Thread(() -> {while (true){try {// 阻塞队列, 只有阻塞的入队列和阻塞的出队列, 没有阻塞的查看队首元素.synchronized (locker) {MyTask myTask = queue.take();long curTime = System.currentTimeMillis();if (myTask.time <= curTime) {// 时间到了, 可以执行任务了myTask.runnable.run();} else {// 时间还没到// 把刚才取出的任务, 重新塞回队列中.queue.put(myTask);locker.wait(myTask.time - curTime);}}} catch (InterruptedException e) {e.printStackTrace();}}});t.start();}
}

注意几个点:

1.要让任务类去实现Comparable接口,以至于可以放进PriorityBlockingQueue

2.使用wait去避免忙等,浪费系统资源

3.在放任务的时候,用notify来唤醒线程

三、完整代码及注意事项

/*** @author xyk的电脑* @version 1.0* @description: TODO* @date 2023/3/25 16:10*/
public class ThreadDemo1 {public static void main(String[] args) {MyTimer myTimer = new MyTimer();myTimer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("hello4");}}, 4000);myTimer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("hello3");}}, 3000);myTimer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("hello2");}}, 2000);myTimer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("hello1");}}, 1000);System.out.println("hello");}}// 表示一个任务.
class MyTask implements Comparable<MyTask>{public Runnable runnable;// 为了方便后续判定, 使用绝对的时间戳.public long time;public MyTask(Runnable runnable, long delay) {this.runnable = runnable;// 取当前时刻的时间戳 + delay, 作为该任务实际执行的时间戳this.time = System.currentTimeMillis() + delay;}@Overridepublic int compareTo(MyTask o) {return (int) (this.time - o.time);}
}class MyTimer{// 这个结构, 带有优先级的阻塞队列. 核心数据结构private PriorityBlockingQueue<MyTask> queue = new PriorityBlockingQueue<>();// 创建一个锁对象private Object locker = new Object();// 此处的 delay 是一个形如 3000 这样的数字 (多长时间之后, 执行该任务)public void schedule(Runnable runnable,long delay){// 根据参数, 构造 MyTask, 插入队列即可.MyTask myTask = new MyTask(runnable,delay);queue.put(myTask);synchronized (locker){locker.notify();}}// 在这里构造线程, 负责执行具体任务了.public MyTimer(){Thread t = new Thread(() -> {while (true){try {// 阻塞队列, 只有阻塞的入队列和阻塞的出队列, 没有阻塞的查看队首元素.synchronized (locker) {MyTask myTask = queue.take();long curTime = System.currentTimeMillis();if (myTask.time <= curTime) {// 时间到了, 可以执行任务了myTask.runnable.run();} else {// 时间还没到// 把刚才取出的任务, 重新塞回队列中.queue.put(myTask);locker.wait(myTask.time - curTime);}}} catch (InterruptedException e) {e.printStackTrace();}}});t.start();}
}

注意事项:

1.使用wait来等待,而不是sleep,wait方便随时提前唤醒

2.wait的参数是“超时时间”,时间达到一定程度后,还没有notify就不等,如果时间还没到,就notify立即返回

3.如果将锁加进内部:

会导致新进来的最早的任务“空打一炮”,导致新的任务无法及时执行了;关键要点:多线程的调度是随机的,无序的!!!