当前位置: 首页 > article >正文

多线程---定时器

文章目录

  • 什么是定时器?
  • 定时器的实现
    • 标准库中的定时器
    • 自己实现一个定时器
      • 版本一:实现简单的定时器
      • 版本二:解决thread线程的忙等问题
      • 版本三:解决漏掉任务的问题
      • 版本四:解决notify先执行的问题

什么是定时器?

定时器的功能和“闹钟”类似,代码中的定时器通常都是“多长时间之后,执行某个动作”。

定时器的实现

标准库中的定时器

        // 标准库的定时器  执行完任务之后线程不会退出  需要手动杀死线程
    public static void main(String[] args) {
        Timer timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("执行第一次定时器任务");
            }
        },3000);

        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("执行第二次定时器任务");
            }
        },4000);

        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("执行第三次定时器任务");
            }
        },5000);

        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("执行第四次定时器任务");
            }
        },6000);

        System.out.println("定时器启动");
    }

注:

  1. 一个定时器可以安排多个任务。
  2. 调用schedule安排任务时,一定要重写run方法,明确任务内容
  3. 定时器开启后不会自动结束,得手动杀死进程

自己实现一个定时器

版本一:实现简单的定时器

  • schedule方法是用来描述任务内容的。第一个参数是:任务内容;第二个参数是:任务多长时间后执行
	public MyTask(Runnable runnable, long delay){
        this.runnable = runnable;
        this.time = System.currentTimeMillis() + delay;
    }

	public void schedule(Runnable runnable,long delay){
        MyTask myTask = new MyTask(runnable, delay); 
    }
  • 用什么数据结构来存储任务呢?

    我们首先要考虑的是要让任务设置时间最短的先执行,这就有个优先级的问题,所以使用优先级队列

    同时,我们通常都会使用多个线程来执行这些设置的任务,就要保证线程安全。

    综上,我们最后决定使用优先级阻塞队列来存储任务

  • 我们创建一个专门的线程来执行定时器里的任务

//最基础的定时器完成
class MyTimer{
    private PriorityBlockingQueue<MyTask> priorityBlockingQueue = new PriorityBlockingQueue<>();



    public MyTimer(){
        Thread thread = new Thread(() -> {
            while (true){
                try {
                    MyTask myTask = priorityBlockingQueue.take();

                    if (System.currentTimeMillis() >= myTask.getTime()){
                        myTask.getRunnable().run();
                    }else {
                        priorityBlockingQueue.put(myTask);

                    }

                } catch (InterruptedException e) {
                    e.printStackTrace();
                }


            }
        });
        thread.start();
    }

    private void schedule(Runnable runnable,long delay){
        MyTask myTask = new MyTask(runnable,delay);
        priorityBlockingQueue.put(myTask);
    }
}

版本二:解决thread线程的忙等问题

  • 当定时器启动后有一个问题:不管任务时间有多久,thread线程一直执行,会占用CPU资源。我们想个办法让它在任务时间还没到的时候停下来,就要使用wait-notify
class MyTimer{
    private PriorityBlockingQueue<MyTask> priorityBlockingQueue = new PriorityBlockingQueue<>();

    private Object locker = new Object();


    public MyTimer(){
        Thread thread = new Thread(() -> {
            while (true){
                try {
                    MyTask myTask = priorityBlockingQueue.take();

                    if (System.currentTimeMillis() >= myTask.getTime()){
                        myTask.getRunnable().run();
                    }else {
                        priorityBlockingQueue.put(myTask);
                        synchronized (locker){
                            locker.wait(myTask.getTime() - System.currentTimeMillis());
                        }

                    }

                } catch (InterruptedException e) {
                    e.printStackTrace();
                }


            }
        });
        thread.start();
    }

    private void schedule(Runnable runnable,long delay){
        MyTask myTask = new MyTask(runnable,delay);
        priorityBlockingQueue.put(myTask);
    }
}

版本三:解决漏掉任务的问题

  • 又一个问题:如果schedule线程中新添加了一个任务 执行时间比wait时间短 不就错过了嘛。使用notify
class MyTimer{
    private PriorityBlockingQueue<MyTask> priorityBlockingQueue = new PriorityBlockingQueue<>();

    private Object locker = new Object();


    public MyTimer(){
        Thread thread = new Thread(() -> {
            while (true){
                try {
                    MyTask myTask = priorityBlockingQueue.take();

                    if (System.currentTimeMillis() >= myTask.getTime()){
                        myTask.getRunnable().run();
                    }else {
                        priorityBlockingQueue.put(myTask);
                        synchronized (locker){
                            locker.wait(myTask.getTime() - System.currentTimeMillis());
                        }

                    }

                } catch (InterruptedException e) {
                    e.printStackTrace();
                }


            }
        });
        thread.start();
    }

    private void schedule(Runnable runnable,long delay){
        MyTask myTask = new MyTask(runnable,delay);
        priorityBlockingQueue.put(myTask);
        synchronized (locker){
            locker.notify();
        }
    }
}

版本四:解决notify先执行的问题

  • 如果notify先执行了 take在notify之前执行 也就是说放进去了新的元素 但是take这边的线程没感觉到不就还没起到效果嘛。 扩大锁的范围
class MyTimer{
    private PriorityBlockingQueue<MyTask> priorityBlockingQueue = new PriorityBlockingQueue<>();

    private Object locker = new Object();


    public MyTimer(){
        Thread thread = new Thread(() -> {
            while (true){
                try {
                    synchronized (locker) {
                        MyTask myTask = priorityBlockingQueue.take();

                        if (System.currentTimeMillis() >= myTask.getTime()) {
                            myTask.getRunnable().run();
                        } else {
                            priorityBlockingQueue.put(myTask);

                            locker.wait(myTask.getTime() - System.currentTimeMillis());


                        }
                    }

                } catch (InterruptedException e) {
                    e.printStackTrace();
                }


            }
        });
        thread.start();
    }

    public void schedule(Runnable runnable,long delay){

        MyTask myTask = new MyTask(runnable, delay);
        priorityBlockingQueue.put(myTask);
        //这里起到的作用就是 刷新一下  看看队列中有没有执行时间更早的任务   有两种情况
        //1. 先放进去再刷新查看    先放进去最开始没注意到也没事儿  刷新的时候就会注意到重新计算wait时间
        //2. 先查看再放进去        会死锁(把notify的锁范围放大)   在一开始队列里没有数据的时候  会等待添加元素 但添加元素得获取到锁  此时阻塞的时候已经锁上了take
        //   没有人释放锁 所以 死锁了
        synchronized (locker) {
            locker.notify();
        }
    }
}

http://www.kler.cn/news/109239.html

相关文章:

  • 【网络安全】Seeker内网穿透追踪定位
  • 0基础学习PyFlink——用户自定义函数之UDF
  • LeetCode热题100 旋转图像
  • 抓包分析DSCP字段在FTP/RSTP协议中的应用
  • 分布式:一文吃透Redis/Zookeeper/MySQL实现分布式锁
  • 【C】C语言文件操作
  • Java-API简析_java.io.FilterOutputStream类(基于 Latest JDK)(浅析源码)
  • mysql 计算两个坐标距离
  • 电路器件认识与KV STUDIO的实践(二)
  • 计算机网络重点概念整理-第三章 数据链路层【期末复习|考研复习】
  • 2.flink编码第一步(maven工程创建)
  • ES性能优化最佳实践- 检索性能提升30倍!
  • word页脚设置,页脚显示第几页共有几页设置步骤
  • hadoop权威指南第四版
  • 天气数据可视化平台-计算机毕业设计vue
  • Java通过工具类判断前端给定的实体类属性中是否为空
  • 深度学习之基于yolov8的安全帽检测系统
  • H5游戏源码分享-命悬一线
  • 软考-网络安全审计技术原理与应用
  • Fedora Linux 38下Mariadb数据库设置utf8mb4字符编码
  • 【2023年冬季】华为OD统一考试(B卷)题库清单(已收录345题),又快又全的 B 卷题库大整理
  • c 从avi 视频中提取图片
  • linux中好玩的数据流定向和管道命令一
  • 【蓝桥每日一题]-前缀和与差分(保姆级教程 篇3)#涂国旗 #重新排序
  • VBA宏查找替换目录下所有Word文档中指定字符串
  • leetcode-数组
  • 计算机网络基础二
  • MATLAB中mse函数用法
  • ✔ ★【备战实习(面经+项目+算法)】 10.29学习
  • 提高抖音小店用户黏性和商品销量的有效策略