赞
踩
定时器是什么?定时器能做什么?本篇文章带你深入了解并实现
目录
一、定时器
定时器是什么?
定时器也是软件开发中的一个重要组件. 类似于一个 "闹钟". 达到一个设定的时间之后, 就执行某个指定好的代码
定时器是一种实际开发中非常常用的组件,比如网络通信中, 如果对方 500ms 内没有返回数据, 则断开连接尝试重连,比如一个 Map, 希望里面的某个 key 在 3s 之后过期,类似于这样的场景就需要用到定时器
标准库提供的定时器:Timer在java.util这个集合类中
标准库中提供了一个 Timer 类,Timer 类的核心方法为 schedule;
schedule 包含两个参数,第一个参数指定即将要执行的任务代码, 第二个参数指定多长时间之后执行 (单位为毫秒)安排一个工作,这个工作不是立即完成的,而是未来某个时间点~~
- public class ThreadDemo6 {
- public static void main(String[] args) {
- Timer timer = new Timer();
- timer.schedule(new TimerTask() {
- @Override
- public void run() {
- System.out.println("hello");
- }
- },4000);
- timer.schedule(new TimerTask() {
- @Override
- public void run() {
- System.out.println("hello");
- }
- },3000);
- timer.schedule(new TimerTask() {
- @Override
- public void run() {
- System.out.println("hello");
- }
- },2000);
- timer.schedule(new TimerTask() {
- @Override
- public void run() {
- System.out.println("hello");
- }
- },1000);
- }
- }
可见线程还没有结束,那这是因为什么呢?
是因为Timer里面内置了线程,(还是前台线程)会阻止线程结束
定时器,内部管理的不仅仅是一个任务,可以管理很多任务的!!
虽然任务有很多,但是他们的触发时间是不同的,每次都找到这些任务中,最先到达的任务执行;一个线程先执行最早的任务,做完了之后再执行第二早的,那么应该用什么去存储这些任务呢?
当然是堆!!!java标准库中提供了带优先级的阻塞队列
- 队列中的每个元素是一个 Task 对象
- Task 中带有一个时间属性, 队首元素就是即将要执行的任务
- 同时有一个 worker 线程一直扫描队首元素, 看队首元素是否需要执行
- // 表示一个任务.
- 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;
- }
-
- @Override
- public 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.要让任务类去实现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() {
- @Override
- public void run() {
- System.out.println("hello4");
- }
- }, 4000);
- myTimer.schedule(new Runnable() {
- @Override
- public void run() {
- System.out.println("hello3");
- }
- }, 3000);
- myTimer.schedule(new Runnable() {
- @Override
- public void run() {
- System.out.println("hello2");
- }
- }, 2000);
- myTimer.schedule(new Runnable() {
- @Override
- public 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;
- }
-
- @Override
- public 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.如果将锁加进内部:
会导致新进来的最早的任务“空打一炮”,导致新的任务无法及时执行了;关键要点:多线程的调度是随机的,无序的!!!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。