• 实现定时器


    目录

    1.定时器是什么

    2.标准库中的定时器

    3.实现定时器


    1.定时器是什么

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

    2.标准库中的定时器

    1. public class TestDemo1 {
    2. public static void main(String[] args) throws InterruptedException {
    3. // java.util 里的一个组件
    4. Timer timer = new Timer();
    5. // schedule : 安排一个任务
    6. // 该任务不是立刻执行,而是延迟多少时间后再执行!!
    7. timer.schedule(new TimerTask() {
    8. @Override
    9. public void run() {
    10. System.out.println("这是一个要执行的任务!");
    11. }
    12. },3000);
    13. // 定时器 和 sleep 不相同
    14. while(true) {
    15. System.out.println("main");
    16. Thread.sleep(1000);
    17. }
    18. }
    19. }

    🍁【注意事项】 

    🍃1.定时器的核心方法是 schedule schedule 包含两个参数,第一个参数指定即将要执行的任务,第二个参数指定多长时间后执行(单位:毫秒)。

    🍃2.将定时器和 sleep 做区分,sleep 是使当前线程处于阻塞状态,而 定时器 只是记录了多长时间后该执行的任务,中间的这些时间,当前线程该干嘛就干嘛。

    🍃3.schedule 里面的 TimerTask 其实就相当于 Runnable,只不过是 TimerTask 实现了 Runnable 接口,在这里我们直接把它当成 Runnable 就好了。

    3.实现定时器

    通过观察标准库中的定时器,我们大概知道要怎么做了!!

    🍃1. Timer 内部要组织很多的任务;

    🍃2. Timer 里的每个任务都要通过一定的方式来描述出来;(自己定义一个 TimerTask

    🍃3. 还需要有一个线程,通过这个线程来扫描定时器内部的任务,执行其中时间到了的任务。(Timer 内部的线程)

    🍁【代码实现】

    1. class MyTask implements Comparable{
    2. // 任务
    3. private Runnable command;
    4. // 任务开始执行的时间(相对时间)
    5. private long time;
    6. public MyTask(Runnable command, long after) {
    7. this.command = command;
    8. // 绝对时间戳
    9. this.time = System.currentTimeMillis() + after;
    10. }
    11. // 执行任务的方法,直接在内部调用 Runnable 的 run 方法即可
    12. public void run() {
    13. command.run();
    14. }
    15. public long getTime() {
    16. return time;
    17. }
    18. @Override
    19. public int compareTo(MyTask o) {
    20. return (int) (this.time - o.time);
    21. }
    22. }
    23. //自己创建的定时器类
    24. class MyTimer {
    25. // 用来阻塞等待的锁对象
    26. private Object locker = new Object();
    27. // 使用优先级阻塞队列来保存若干个任务
    28. private PriorityBlockingQueue queue = new PriorityBlockingQueue<>();
    29. // command : 要执行的任务是啥
    30. // after : 任务啥时候执行
    31. public void schedule(Runnable command, long after) {
    32. MyTask myTask = new MyTask(command, after);
    33. synchronized (locker) {
    34. queue.put(myTask);
    35. locker.notify();
    36. }
    37. }
    38. public MyTimer() {
    39. //在这里启动一个线程
    40. Thread t = new Thread(() -> {
    41. while(true) {
    42. //循环过程中,就不断的尝试从队列中获取到队首元素
    43. //判断队首元素当前的时间是否就绪,如果就绪了就执行,不就绪,就不执行
    44. try {
    45. synchronized (locker) {
    46. //队列为空,也要等待
    47. if(queue.isEmpty()) {
    48. locker.wait();
    49. }
    50. //取出队首任务
    51. MyTask myTask = queue.take();
    52. long curTime = System.currentTimeMillis();
    53. if(myTask.getTime() > curTime) {
    54. //时间还没到,就放回去
    55. queue.put(myTask);
    56. //等待最早的任务开始的时间和当前时间的差值
    57. locker.wait(myTask.getTime() - curTime);
    58. } else {
    59. //时间到了,就执行任务
    60. myTask.run();
    61. }
    62. }
    63. } catch (InterruptedException e) {
    64. e.printStackTrace();
    65. }
    66. }
    67. });
    68. t.start();
    69. }
    70. }

    【分析实现过程中的重点步骤】

    🍁重点 1:使用优先级阻塞队列将我们的任务组织起来!!

    🍃1.虽然任务可能有很多,但是它们的执行顺序是一定的,且按照时间顺序先后来执行的,所以使用优先级队列。

    🍃2.在多线程环境下,这个队列会被多个线程访问,第一,schedule 可能是在多线程中被调用,每次调用都要往队里添加元素;第二,定时器内部还需要有专门的线程来执行队列里的任务。这些操作在多线程里都是存在线程安全问题的,所以需要使用到优先级阻塞队列!!

    🍁重点2:优先级阻塞队列里的元素是一个引用类型(MyTask),所以我们需要指定比较规则,既可以让 MyTask 实现 Comparable 接口,也可以在优先级阻塞队列的构造方法中传参,传一个比较器 Comparator !!

    🍁重点3:执行任务的时候:队列不为空时,我们先将元素取出来,判断是否到了执行时间,没到时间就放回去,放回去之后要加上 wait() 。那么加入新任务的时候也就相应的要 notify() 避免让 CPU 出现"空转"的现象!!

     

    🍁重点4:我们的加锁不能只包裹 wait() ,notify(),否则会出现非原子性操作,从而导致线程安全问题!!

     

    经过上述分析,我们发现多线程的代码真的是防不胜防,稍微一点不注意,都可能引起线程安全问题!!


  • 相关阅读:
    UI设计中色彩运用应该注意哪些问题 优漫动游
    Redis基本操作
    javaScript贪吃蛇
    c++面试题汇总-58-118
    DevOps的流程与规范介绍
    周四见|物流人的一周资讯
    在nodejs中使用typescript
    【uniapp基础篇】实现微信登录
    【JUC系列-06】深入理解Semaphore底层原理和基本使用
    2022就业季|Spring认证教你,如何使用 Spring 构建 REST 服务
  • 原文地址:https://blog.csdn.net/xaiobit_hl/article/details/126055816