多线程案例(3)

文章目录

  • 多线程案例三
  • 三、 定时器

大家好,我是晓星航。今天为大家带来的是 多线程案例三 相关的讲解!😀

多线程案例三

三、 定时器

定时器是什么

定时器也是软件开发中的一个重要组件. 类似于一个 “闹钟”. 达到一个设定的时间之后, 就执行某个指定 好的代码.

定时器是一种实际开发中非常常用的组件.

比如网络通信中, 如果对方 500ms 内没有返回数据, 则断开连接尝试重连.

比如一个 Map, 希望里面的某个 key 在 3s 之后过期(自动删除).

类似于这样的场景就需要用到定时器.

这个方法的效果是,给定时器,注册一个任务。任务不会立即执行,而是在指定时间进行执行。

标准库中的定时器

  • 标准库中提供了一个 Timer 类. Timer 类的核心方法为schedule.
  • schedule 包含两个参数. 第一个参数指定即将要执行的任务代码, 第二个参数指定多长时间之后 执行 (单位为毫秒).
System.out.println("程序启动");
//这个 Timer 类就是标准库的定时器
Timer timer = new Timer();
timer.schedule(new TimerTask() {
    @Override
    public void run() {
        System.out.println("运行定时器任务1");
    }
},1000);
timer.schedule(new TimerTask() {
    @Override
    public void run() {
        System.out.println("运行定时器任务2");
    }
},2000);
timer.schedule(new TimerTask() {
    @Override
    public void run() {
        System.out.println("运行定时器任务3");
    }
},3000);

实现定时器

定时器的构成:

  • 一个带优先级的阻塞队列

为啥要带优先级呢?

因为阻塞队列中的任务都有各自的执行时刻 (delay). 最先执行的任务一定是 delay 最小的. 使用带 优先级的队列就可以高效的把这个 delay 最小的任务找出来.

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

1)Timer 类提供的核心接口为 schedule, 用于注册一个任务, 并指定这个任务多长时间后执行.

public class Timer {
    public void schedule(Runnable command, long after) {
 // TODO
   }
}

2)Task 类用于描述一个任务(作为 Timer 的内部类). 里面包含一个 Runnable 对象和一个 time(毫秒时 间戳)

这个对象需要放到 优先队列 中. 因此需要实现 Comparable 接口.

static class Task implements Comparable<Task> {
        private Runnable command;
        private long time;
        public Task(Runnable command, long time) {
            this.command = command;
            // time 中存的是绝对时间, 超过这个时间的任务就应该被执行
            this.time = System.currentTimeMillis() + time;
       }
        public void run() {
            command.run();
       }
        @Override
        public int compareTo(Task o) {
            // 谁的时间小谁排前面
            return (int)(time - o.time);
       }
   }
}

3)Timer 实例中, 通过 PriorityBlockingQueue 来组织若干个 Task 对象.

通过 schedule 来往队列中插入一个个 Task 对象.

class Timer {
    // 核心结构
    private PriorityBlockingQueue<Task> queue = new PriorityBlockingQueue();
    
    public void schedule(Runnable command, long after) {
        Task task = new Task(command, after);
        queue.offer(task);
   }    
}

4)Timer 类中存在一个 worker 线程, 一直不停的扫描队首元素, 看看是否能执行这个任务

所谓 “能执行” 指的是该任务设定的时间已经到达了.

class Timer {
 // ... 前面的代码不变
    
    public Timer() {
        // 启动 worker 线程
        Worker worker = new Worker();
        worker.start();
   }
    
    class Worker extends Thread{
        @Override
        public void run() {
            while (true) {
                try {
                    Task task = queue.take();
                    long curTime = System.currentTimeMillis();
                    if (task.time > curTime) {
                        // 时间还没到, 就把任务再塞回去
queue.put(task);
                   } else {
                        // 时间到了, 可以执行任务
task.run();
                   }
               } catch (InterruptedException e) {
                    e.printStackTrace();
                    break;
               }
           }
       }
   }
}

但是当前这个代码中存在一个严重的问题, 就是 while (true) 转的太快了, 造成了无意义的 CPU 浪费.

比如第一个任务设定的是 1 min 之后执行某个逻辑. 但是这里的 while (true) 会导致每秒钟访问队 首元素几万次. 而当前距离任务执行的时间还有很久呢.

5)引入一个 mailBox 对象, 借助该对象的 wait / notify 来解决 while (true) 的忙等问题.

class Timer {
    // 存在的意义是避免 worker 线程出现忙等的情况
    private Object mailBox = new Object(); 
}

修改 Worker 的 run 方法, 引入 wait, 等待一定的时间.

public void run() {
    while (true) {
        try {
            Task task = queue.take();
            long curTime = System.currentTimeMillis();
            if (task.time > curTime) {
                // 时间还没到, 就把任务再塞回去
                queue.put(task);
                // [引入 wait] 等待时间按照队首元素的时间来设定. 
                synchronized (mailBox) {
                    // 指定等待时间 wait
                    mailBox.wait(task.time - curTime);
               }
                
           } else {
                // 时间到了, 可以执行任务
                task.run();
           }
       } catch (InterruptedException e) {
            e.printStackTrace();
            break;
       }
   }
}

修改 Timer 的 schedule 方法, 每次有新任务到来的时候唤醒一下 worker 线程. (因为新插入的任务可能 是需要马上执行的).

public void schedule(Runnable command, long after) {
    Task task = new Task(command, after);
    queue.offer(task);
    
    // [引入 notify] 每次有新的任务来了, 都唤醒一下 worker 线程, 检测下当前是否有
    synchronized (mailBox) {
        mailBox.notify();
   }
}

完整代码

/**
* 定时器的构成:
* 一个带优先级的阻塞队列
* 队列中的每个元素是一个 Task 对象.
* Task 中带有一个时间属性, 队首元素就是即将
* 同时有一个 worker 线程一直扫描队首元素, 看队首元素是否需要执行
*/
public class Timer {
    static class Task implements Comparable<Task> {
        private Runnable command;
        private long time;
        public Task(Runnable command, long time) {
            this.command = command;
            // time 中存的是绝对时间, 超过这个时间的任务就应该被执行
            this.time = System.currentTimeMillis() + time;
       }
        public void run() {
            command.run();
       }
 @Override
        public int compareTo(Task o) {
            // 谁的时间小谁排前面
            return (int)(time - o.time);
       }
   }
    // 核心结构
    private PriorityBlockingQueue<Task> queue = new PriorityBlockingQueue();
    // 存在的意义是避免 worker 线程出现忙等的情况
    private Object mailBox = new Object();
    class Worker extends Thread{
        @Override
        public void run() {
            while (true) {
                try {
                    Task task = queue.take();
                    long curTime = System.currentTimeMillis();
                    if (task.time > curTime) {
                        // 时间还没到, 就把任务再塞回去
queue.put(task);
                        synchronized (mailBox) {
                            // 指定等待时间 wait
mailBox.wait(task.time - curTime);
                       }
                   } else {
                        // 时间到了, 可以执行任务
task.run();
                   }
               } catch (InterruptedException e) {
                    e.printStackTrace();
                    break;
               }
           }
       }
   }
    public Timer() {
        // 启动 worker 线程
        Worker worker = new Worker();
        worker.start();
   }
    // schedule 原意为 "安排"
    public void schedule(Runnable command, long after) {
        Task task = new Task(command, after);
        queue.offer(task);
        synchronized (mailBox) {
            mailBox.notify();
       }
   }
    public static void main(String[] args) {
        Timer timer = new Timer();
        Runnable command = new Runnable() {
            @Override
            public void run() {
             System.out.println("我来了");
                timer.schedule(this, 3000);
           }
       };
        timer.schedule(command, 3000);
   }
}

我们自己写的定时器:

咱们的定时器里面,核心

1.要有一个扫描线程,负责判定时间到/执行任务

2.还要有一个数据结构,来保存所有被注册的任务

我们在当前场景下,使用优先级队列,是一个很好的选择!!!

按照时间小的,作为优先级高的,此时队首元素就是整个队列中,最先要执行的任务。此时扫描线程只需扫一下队首元素即可。不必遍历整个队列。(如果队首元素还没到执行时间,后续元素更不可能到时间!!!)

此时我们自己写的定时器基本框架就已经搭构完成,我们用MyTask这个类来创建定义要执行的任务runnable和时间戳time,而后在MyTimer中使用他们

阻塞队列,只能先把元素出队列,才好判定,不满足还得塞回去。这不像普通队列,可以直接取队首元素判定的。

定时器(自己版本)完整版:

import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.PriorityBlockingQueue;
/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: 晓星航
 * Date: 2023-07-29
 * Time: 11:20
 */
//使用这个类来表示一个定时器中的任务.
class MyTask implements Comparable<MyTask>{
    //要执行的任务内容
    private Runnable runnable;
    //任务啥时候执行,(使用毫秒时间戳表示)
    private long time;

    public MyTask(Runnable runnable, long time) {
        this.runnable = runnable;
        this.time = time;
    }
    //获取当前任务的时间
    public long getTime() {
        return time;
    }
    //执行任务
    public void run() {
        runnable.run();
    }

    @Override
    public int compareTo(MyTask o) {
        //返回小于0 大于0  0
        //this 比 o 小,返回 < o
        //this 比 o 小,返回 > o
        //this 和 o 相等,返回 = o
        //当前要实现的效果,是队首元素 是时间最小的任务
        //这俩谁减谁不要去记!!!试试就知道了。
        //要么是 this.time - o.time   要么是 o.time - this.time
        return (int)(this.time - o.time);
    }
}

//自己写的简单的定时器
class MyTimer {
    //扫描线程
    private Thread t = null;

    //有一个阻塞优先级队列,来保存任务。
    private PriorityBlockingQueue<MyTask> queue = new PriorityBlockingQueue<>();

    public MyTimer() {
        t = new Thread(()->{
            while (true) {
                try {
                    //取出队首元素,检查看看队首元素任务是否到时间了。
                    //如果时间没到,就把任务塞回队列里去。
                    //如果时间到了,就把任务进行执行。
                    synchronized (this) {
                        MyTask myTask = queue.take();
                        long curTime = System.currentTimeMillis();
                        if (curTime < myTask.getTime()) {
                            //还没到点,先不必执行
                            queue.put(myTask);
                            //在 put 之后,进行一个 wait
                                this.wait(myTask.getTime() - curTime);
                        } else {
                            //时间到了!!执行任务!!
                            myTask.run();
                        }
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t.start();
    }

    //指定两个参数
    //第一个参数是 任务 内容
    //第二个参数是 任务 在多少毫秒之后执行,形如 1000
    public void schedule(Runnable runnable,long after) {
        //注意这里的时间上的换算
        MyTask task = new MyTask(runnable,System.currentTimeMillis() + after);
        queue.put(task);
        synchronized (this) {
            this.notify();
        }
    }
}
public class ThreadDemo25 {
    public static void main(String[] args) {
        MyTimer myTImer = new MyTimer();
        myTImer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("任务1");
            }
        },1000);
        myTImer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("任务2");
            }
        },2000);
        myTImer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("任务3");
            }
        },3000);
    }
}

从运行结果不难看出,我们自己写的定时器和API自带的Timer是一样的,都会按照对应的时间进行启动。

感谢各位读者的阅读,本文章有任何错误都可以在评论区发表你们的意见,我会对文章进行改正的。如果本文章对你有帮助请动一动你们敏捷的小手点一点赞,你的每一次鼓励都是作者创作的动力哦!😘

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/56856.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

API教程:轻松上手HTTP代理服务!

作为HTTP代理产品供应商&#xff0c;我们为您带来一份详细的教程&#xff0c;帮助您轻松上手使用API&#xff0c;并充分利用HTTP代理服务。无论您是开发人员、网络管理员还是普通用户&#xff0c;本教程将为您提供操作指南和代码模板&#xff0c;确保您能够顺利使用API并享受HT…

基于埋点日志数据的网络流量统计 - PV、UV

水善利万物而不争&#xff0c;处众人之所恶&#xff0c;故几于道&#x1f4a6; 文章目录 一、 网站总流量数统计 - PV 1. 需求分析 2. 代码实现 方式一 方式二 方式三&#xff1a;使用process算子实现 方式四&#xff1a;使用process算子实现 二、网站独立访客数统计 - UV 1. …

【雕爷学编程】MicroPython动手做(28)——物联网之Yeelight 5

知识点&#xff1a;什么是掌控板&#xff1f; 掌控板是一块普及STEAM创客教育、人工智能教育、机器人编程教育的开源智能硬件。它集成ESP-32高性能双核芯片&#xff0c;支持WiFi和蓝牙双模通信&#xff0c;可作为物联网节点&#xff0c;实现物联网应用。同时掌控板上集成了OLED…

安防视频综合管理合平台EasyCVR可支持的视频播放协议有哪些?

EasyDarwin开源流媒体视频EasyCVR安防监控平台可提供视频监控直播、云端录像、云存储、录像检索与回看、智能告警、平台级联、云台控制、语音对讲、智能分析等能力。 视频监控综合管理平台EasyCVR具备视频融合能力&#xff0c;平台基于云边端一体化架构&#xff0c;具有强大的…

2023 7.31~8.6 周报 (多尺度的DL-FWI + 自然图像的风格迁移速度模型)

->目录<- 0 上周回顾1 本周论文背景简述2 模型架构3 风格化速度模型4 训练与实际数据的测试5 存在的一些问题6 总结和下一步工作 0 上周回顾 上周完成了VelocityGAN的重现和学习. 认识到了利用判别器网络对于常规网络进行约束是很一种很高效的设计思路. 1 本周论文背景…

【物理】带电粒子在磁场和电场中移动的 3D 轨迹研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

innovus: 让ndr使用自定义via def

我正在「拾陆楼」和朋友们讨论有趣的话题&#xff0c;你⼀起来吧&#xff1f; 拾陆楼知识星球入口 让ndr 使用指定via def可以用add_ndr -via命令&#xff0c;如果现有的via list无法满足要求&#xff0c;可以用 add_via_definition -via_rule -row_col去创建。

天工开物 #7 Rust 与 Java 程序的异步接口互操作

许多语言的高性能程序库都是建立在 C/C 的核心实现上的。 例如&#xff0c;著名 Python 科学计算库 Pandas 和 Numpy 的核心是 C 实现的&#xff0c;RocksDB 的 Java 接口是对底层 C 接口的封装。 Rust 语言的基本目标之一就是替代 C 在这些领域的位置&#xff0c;为开发者提供…

小程序如何上传商品图片

了解如何在小程序商城中上传商品图片是非常重要的&#xff0c;因为商品图片的质量和展示效果直接影响到用户对商品的购买决策。下面&#xff0c;我将介绍怎么在小程序上传产品图片的方法和注意事项。 1. 图片准备&#xff1a;在上传商品图片之前&#xff0c;首先要准备好商品图…

CTFSHOW php 特性

web89 数组绕过正则 include("flag.php"); highlight_file(__FILE__);if(isset($_GET[num])){$num $_GET[num]; get numif(preg_match("/[0-9]/", $num)){ 是数字 就输出 nodie("no no no!");}if(intval($num)){ 如果是存在整数 输出 flagecho …

代码调试2:coco数据集生成深度图

代码调试:coco数据集生成深度图 作者:安静到无声 个人主页 问题1:图片存在异常,跳过不处理 在获取深度图的时候,直接执代码,会产生以下错误:RuntimeError和ValueError。 因此我重新修改了代码,如果出现以下两种错误,则执行下一次循环,代码如下: 修改之后代码可以…

二叉树的前,中,后序的非递归实现(c++)

前言 对于二叉树来说&#xff0c;遍历它有多种方式&#xff0c;其中递归遍历是比较简单的&#xff0c;但是非递归的实现就有一定的难度&#xff0c;在这里介绍一种非递归实现二叉树遍历的方式。 1.前序遍历 1.1思路 其实对于二叉树的非递归实现&#xff0c;实际上就是用代码来…

【JAVA】 javaSE中的数组|数组的概念使用

数组的概念 什么是Java中的数组 数组&#xff1a;可以看成是相同类型元素的一个集合。在内存中是一段连续的空间。在java中&#xff0c;包含6个整形类型元素的数组&#xff0c;可以看做是酒店中连续的6个房间. 1. 数组中存放的元素其类型相同 2. 数组的空间是连在一起的 3…

哪些情况下需要使用爬虫IP

不知道小伙伴们有没有遇到过这种场景&#xff1a;上网闲逛&#xff0c;看一些搞笑的视频或者想下载一些酷炫的文件&#xff0c;正点击呢&#xff0c;结果却发现被网站限制了&#xff0c;无法访问或者下载&#xff1f; 别急&#xff0c;今天我来告诉大家&#xff0c;如何借助使…

【JavaEE初阶】博客系统后端

文章目录 一. 创建项目 引入依赖二. 设计数据库三. 编写数据库代码四. 创建实体类五. 封装数据库的增删查改六. 具体功能书写1. 博客列表页2. 博客详情页3. 博客登录页4. 检测登录状态5. 实现显示用户信息的功能6. 退出登录状态7. 发布博客 一. 创建项目 引入依赖 创建blog_sy…

yolov3-spp 训练结果分析:网络结果可解释性、漏检误检分析

1. valid漏检误检分析 ①为了探查第二层反向找出来的目标特征在最后一层detector上的意义&#xff01;——为什么最后依然可以框出来目标&#xff0c;且mAP还不错的&#xff1f; ②如何进一步提升和改进这个数据的效果&#xff1f;可以有哪些优化数据和改进的地方&#xff1f;让…

页面技术基础-html

页面技术基础-html 环境准备&#xff1a;在JDBC中项目上完成代码定义 1. 新建一个 Module:filr->右键 -》Module -》Java-》next->名字(html_day1)->finish 2. 在 Moudle上右键-》第二个选项&#xff1a;add framework .. -> 选择JavaEE下第一个选项 Web Apllicat…

计及需求响应和电能交互的多主体综合能源系统主从博弈优化调度策略(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

通向架构师的道路之apache_tomcat_https应用

一、总结前一天的学习 通过上一章我们知道、了解并掌握了Web Server结合App Server是怎么样的一种架构&#xff0c;并且亲手通过Apache的Http Server与Tomcat6进行了整合的实验。 这样的架构的好处在于&#xff1a; 减轻App Server端的压力&#xff0c;用Web Server来分压…

python——案例8:设定列表:listl=[0,1,2,3,4,5],求列表之和

案例8&#xff1a;设定列表&#xff1a;listl[0,1,2,3,4,5],求列表之和total0 list1[0,1,2,3,4,5] #列表lis1for ele in range(0,len(list1)):totaltotallist1[ele] print("列表中元素之和&#xff1a;",total) #输出结果