【Java EE初阶八】多线程案例(计时器模型)

1. java标准库的计时器

1.1 关于计时器

        计时器类似闹钟,有定时的功能,其主要是到时间就会执行某一操作,即可以指定时间,去执行某一逻辑(某一代码)。

1.2 计时器的简单介绍

        在java标准库中,提供了Timer类,Timer类的核心方法是schedule(里面包含两个参数,一个是要执行的任务代码,一个是设置多久之后执行这个任务代码的时间

        注意:Timer内置了线程(前台线程),代码如下所示:

package thread;import java.util.Timer;
import java.util.TimerTask;public class ThreadDemo30 {public static void main(String[] args) throws InterruptedException {Timer timer = new Timer();timer.schedule(new TimerTask() {@Overridepublic void run() {// 时间到了之后, 要执行的代码System.out.println("hello timer 3000");}}, 3000);timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("hello timer 2000");}}, 2000);timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("hello timer 1000");}}, 1000);System.out.println("hello main");Thread.sleep(5000);timer.cancel();}
}

        结果如下图所示:

        代码分析:

        如上图所示,先打印 hello main ,等过了1s才打印 hello 1000,说明Timer内置了线程,main线程不用等待,而timer类是要到时间才会执行任务代码。

        为什么这里可以看到idea里显示线程结束,因为timer类里面有cancel方法,可以结束线程,我们把cancel方法加到打印hello 3000那方法里面,这样就可以结束timer类里面的线程了。

        注意:timer类里面内置的是前台线程,会阻止线程提前结束。

2.  模拟实现一个计时器   

2.1 设计思路

        1、计数器中要存放任务的数据结构
        首先,计时器可以定时去执行一些任务操作,那么我们怎么每次先去执行时靠前的那一操作呢?

        其实在某一些场景下确实可以用数组,但这就需要我们每次都去遍历数组,找出最靠前的时间,但是如果我们要定时很多任务,都需要先找到时间靠前的任务,这就不合理了;从数组里面找出这个时间最靠前的任务数据,一方面要考虑资源花销大的问题,还有要考虑时间的问题,找任务的时间太长,错过了已经到时要执行的任务,如上所述说明使用数组存放任务是不合理的。

        所以就引入了优先级队列,这样每次拿都能拿到时间最小的任务,时间复杂度也仅仅是O(1),但是优先级队列不能是阻塞队列,否则会引起死锁问题。

        2、存放优先级队列中的任务类型:

        我们自定义为任务类MyTimerTask
        任务类是放要执行的代码和要执行任务时间,单独作为一类,存进优先级队列中,其中,优先级队列里的比较规则是按任务类设定的执行时间先后(即时间的大小)来比较的。

        3、计数器类MyTimer
        我们设计一个线程,放在MyTimer类的构造方法中,这个线程就是扫描线程,我们使用该扫描线程来完成判断和操作,主要是入队列或者判断啥时候才执行要执行的代码的操作;同时创建任务schedule的方法里面也包含有入队列的操作。

   2.2 代码实现

        1、MyTimer类:

// 通过这个类, 来表示一个定时器
class MyTimer {// 负责扫描任务队列, 执行任务的线程.private Thread t = null;// 任务队列private PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();// 搞个锁对象, 此处使用 this 也可以.private Object locker = new Object();public void schedule(Runnable runnable, long delay) {synchronized (locker) {MyTimerTask task = new MyTimerTask(runnable, delay);queue.offer(task);// 添加新的元素之后, 就可以唤醒扫描线程的 wait 了.locker.notify();}}public void cancel() {// 结束 t 线程即可// interrupt}// 构造方法. 创建扫描线程, 让扫描线程来完成判定和执行.public MyTimer() {t = new Thread(() -> {// 扫描线程就需要循环的反复的扫描队首元素, 然后判定队首元素是不是时间到了.// 如果时间没到, 啥都不干// 如果时间到了, 就执行这个任务并且把这个任务从队列中删除掉.while (true) {try {synchronized (locker) {while (queue.isEmpty()) {// 暂时先不处理locker.wait();}MyTimerTask task = queue.peek();// 获取到当前时间long curTime = System.currentTimeMillis();if (curTime >= task.getTime()) {// 当前时间已经达到了任务时间, 就可以执行任务了.queue.poll();task.run();} else {// 当前时间还没到, 暂时先不执行// 不能使用 sleep. 会错过新的任务, 也无法释放锁.// Thread.sleep(task.getTime() - curTime);locker.wait(task.getTime() - curTime);}}} catch (InterruptedException e) {e.printStackTrace();}}});// 要记得 start !!!!t.start();}
}

        里面的核心模块:

        1、schedule方法,该方法的创建任务,里面包含了要执行的代码和执行代码的时间,            2、构造方法,里面有一个线程,该线程就是不断去判断队列有没有任务,如果有任务的话,就去找最先执行的任务,等到该任务执行时间就执行扫描到的该任务,如果没到达执行时间的话就要等。

2、MyTimerTask任务类:

// 通过这个类, 来描述一个任务
class MyTimerTask implements Comparable<MyTimerTask> {// 在什么时间点来执行这个任务.// 此处约定这个 time 是一个 ms 级别的时间戳.private long time;// 实际任务要执行的代码.private Runnable runnable;public long getTime() {return time;}// delay 期望是一个 "相对时间"public MyTimerTask(Runnable runnable, long delay) {this.runnable = runnable;// 计算一下真正要执行任务的绝对时间. (使用绝对时间, 方便判定任务是否到达时间的)this.time = System.currentTimeMillis() + delay;}public void run() {runnable.run();}@Overridepublic int compareTo(MyTimerTask o) {return (int) (this.time - o.time);// return (int) (o.time - this.time);}
}

        该任务类里面放的是要执行的任务,和任务执行的延迟时间时间,因为任务要放进优先级队列里,所以要构造一个比较器,用时间参数来进行比较,并且重写compareTo方法,将比较规则具体化。

2.3 计时器的线程安全

        1、维护队列进出的操作---加锁

        不创建其他线程,如果只有一个主线程去调用MyTimer类的话,此时就会有主线程main和 t 线程,这时候,存在线程不安全问题的主线程的代码如下所示:

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

        关于主线程main与t线程存在的线程安全图解如下: 

        多线程运行时,会出现同一时刻一个队列存在多个任务进有出的情况,会导致线程不安全;所以,要维护这个队列,就要把入队列和出队列操作都上锁,同一时间要么只能入队列,要么只能出队列;

        对于入队列操作上锁的位置范围,就是把创建任务和入队列操作都上锁;

        对于出队列操作上锁的位置范围,我们要考虑是否把while循环都给上锁了,显然易见,把while上锁的代码十分危险,在我们当前的场景上确实可以用;但是,在其他场景下,如果一个线程拿到锁了,系统就会不停的解锁、加锁,这样会导致其他线程饿死了,所以在while里面加锁,是比较大众的;

        2、优先级队列为空时,设置阻塞等待功能

        3、任务没到执行时间,要让该任务等待到固定时间在执行

        代码完善部分如下所示:

        代码详解:

        没到任务执行的时间,就要让该任务阻塞等待,且等待时间是: 任务执行的时刻 - 当前的时刻,没有限制要等待的时间的话,就会一直循环,每次循都会环判断是不是到任务执行的时间了,反复循环这个代码执行速度是很快的,但是就会盲等,由此我们不设置任务执行时间的话就会导致计算机资源的浪费;

ps:本次关于计时器的内容就到这里了,如果对大家有所帮助的话,就请一键三连,当然内容可能还会更新,因为未完待续嘛!!!

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

阿里云服务器Centos安装宝塔面板

阿里云服务器Centos安装宝塔面板 1 背景1.1 aliyun1.2 Linux 2 安装步骤2.0 环境配置2.1 安装前准备2.2 宝塔安装2.3 建站 3 centos常用命令3.1 防火墙相关 1 背景 1.1 aliyun 阿里云服务器是阿里云提供的一项云计算服务&#xff0c;它能够帮助用户快速搭建网站、应用和服务&…

苍穹外卖Day01——总结1

总结1 1. 软件开发整体介绍1.1 软件开发流程1.2 角色分工1.3 软件环境 2. 苍穹外卖项目介绍2.1 项目介绍2.2 技术选项 3. Swagger4. 补充内容&#xff08;待解决...&#xff09; 1. 软件开发整体介绍 1.1 软件开发流程 1.2 角色分工 从角色分工里面就可以查看自己以后从事哪一…

寄10公斤包裹哪个快递便宜(寄快递哪个比较便宜)

如今&#xff0c;随着互联网的发展&#xff0c;越来越多的人选择网上购物&#xff0c;这支撑了许多物流公司不断地向前发展。所以快递行业的前景还是很光明的。现在当天寄出最晚第二天就能收到。但是快递公司那么多&#xff0c;每个公司的特色和收费都有差异。怎样才能选择合适…

windows通过ssh连接Liunx服务器并实现上传下载文件

连接ssh 输入&#xff1a;ssh空格用户名ip地址&#xff0c;然后按Enter 有可能出现下图提示&#xff0c;输入yes 回车即可 输入 password &#xff0c;注意密码是不显示的&#xff0c;输入完&#xff0c;再按回车就行了 以上是端口默认22情况下ssh连接&#xff0c;有些公司它…

微信小程序 获取地址信息(uniapp)

参考API地址&#xff1a;微信小程序JavaScript SDK | 腾讯位置服务 <script> // 引入SDK核心类&#xff0c;js文件根据自己业务&#xff0c;位置可自行放置var QQMapWX require(../../js/uploadImg/qqmap-wx-jssdk.js);export default {data(){return{qqmapsdk:}},onL…

Linux_CentOS_7.9_Oracle11gr2配置数据库及监听服务自启动多方案实操之简易记录

前言: 作为运维保障,都无法准确预估硬件宕机的突发阶段,其生产数据实时在产出,那作为dba数据库服务以及相关Listener的其重要性、必要性就突显而出。这里拿虚拟机试验做个配置记录,便于大家学习参考。 实现方法一: 环境变量值::$ORACLE_HOME= /data/oracle/product/1…

NVIDIA Container Toolkit(NVIDIA Docker)

引言 Nvidia Docker该项目已被NVIDIA Container Toolkit取代。此存储库提供的工具已被弃用&#xff0c;并且该存储库已存档。 nvidia-docker不再支持包装器&#xff0c;并且 NVIDIA Container Toolkit 已进行扩展&#xff0c;允许用户配置 Docker 以使用 NVIDIA Container Ru…

Excel5:自动化周报的制作

自动化周报的数据引用来源于8月成交数据-纯数值表格&#xff0c;因为8月成交数据表格中部分单元格中有vlookup函数&#xff0c;且存在跨表连接。 对于跨表连接的解释和说明&#xff1f; 首先打开我们之前做好的成交数据。打开后我们可以看到这上面出现了一个安全警告&#xff0…

什么是SEO?SEO还存在吗?

曾经火热的seo&#xff0c;至今为啥很少人知道呢&#xff1f;为啥说seo是曾经的火热&#xff0c;这还得从那时百度的算法来说起了&#xff0c;曾经的百度可以通过seo优化自己的网站来获得百度爬虫的爬取&#xff0c;从而在百度获得更高的排名和权重。 现在我们打开百度随便搜索…

zabbix部署

zabbix部署 部署zabbix服务被监测主机部署zabbix-agent2 使用版本 组件版本centos7.9zabbix5.0php7.2.24MariaDB5.5.68 部署zabbix服务 关闭防火墙和selinux [rootnode ~]# systemctl status firewalld ● firewalld.service - firewalld - dynamic firewall daemonLoaded: …

LeetCode-棒球比赛(682)

题目描述&#xff1a; 你现在是一场采用特殊赛制棒球比赛的记录员。这场比赛由若干回合组成&#xff0c;过去几回合的得分可能会影响以后几回合的得分。 比赛开始时&#xff0c;记录是空白的。你会得到一个记录操作的字符串列表 ops&#xff0c;其中 ops[i] 是你需要记录的第…

C#: Label、TextBox 鼠标停留时显示提示信息

说明&#xff1a;记录在 Label、TextBox 控件上 鼠标停留时显示提示信息的方法。 1.效果图 2.具体实现步骤 1. 在Form 窗口中先创建 Label 并取名&#xff1a;KEY_label &#xff0c;或 TextBox 取名&#xff1a;KEY_textBox 2. lable控件的 tips 实现方法1 &#xff1a;代码…

18款Visual Studio实用插件(更新)

前言 俗话说的好工欲善其事必先利其器&#xff0c;安装一些Visual Studio实用插件对自己日常的开发和工作效率能够大大的提升&#xff0c;避免996从选一款好的IDE实用插件开始。以下是我认为比较实用的Visual Studio插件希望对大家有用&#xff0c;大家有更好的插件推荐可在文…

用cmd打开mysql显示拒绝访问

使用winR键输入cmd&#xff0c;打开命令行窗口提示符&#xff0c;输入net start mysql 出现&#xff1a;发生系统错误 5&#xff0c;拒绝访问 如何解决 同样的步骤&#xff0c;我们使用winR键输入cmd打开命令行提示窗口提示符&#xff0c; 按住ctrlshiftEnter键使用管理员打…

嵌入式-C语言-江科大-指针的详解与应用

文章目录 一&#xff1a;计算机存储机制二&#xff1a;定义指针三&#xff1a;指针的操作四&#xff1a;数组与指针五&#xff1a;指针的应用道友&#xff1a;最清晰的脚印&#xff0c;踩在最泥泞的道路上。 推荐视频配合我的笔记使用 [C语言] 指针的详解与应用-理论结合实践&a…

docker 部署haproxy cpu占用特别高

在部署mysql 主主高可用时&#xff0c;使用haproxy进行负载&#xff0c;在服务部使用的情况下发现服务器cpu占比高&#xff0c;负载也高&#xff0c;因此急需解决这个问题。 1.解决前现状 1.1 部署配置文件 cat > haproxy.cfg << EOF globalmaxconn 4000nbthrea…

组态王与S7-1200PLC之间 Profinet无线以太网通信

组态王与S7-1200PLC之间想要搭建 Profinet无线以太网通信&#xff0c;需要用到以下设备&#xff1a; ● 西门子PLC型号&#xff1a;S7-1200 2台 ● 上位机&#xff1a;组态王6.55 1台 ● 无线通讯终端&#xff1a;DTD418MB 3块 ● 主从关系&#xff1a;1主2从 ● 通讯接…

php-ffmpeg运用 合并视频,转码视频

下载 官网 windows 版本 添加环境变量 合并视频 public function test_that_true_is_true(): void{ini_set(memory_limit,-1); //没有内存限制set_time_limit(0);//不限制执行时间//ffmpeg配置$path [ffmpeg.binaries > D:\soft\ffmpeg\bin/ffmpeg.exe,ffprobe.binaries…

uniapp微信小程序投票系统实战 (SpringBoot2+vue3.2+element plus ) -用户信息修改实现

锋哥原创的uniapp微信小程序投票系统实战&#xff1a; uniapp微信小程序投票系统实战课程 (SpringBoot2vue3.2element plus ) ( 火爆连载更新中... )_哔哩哔哩_bilibiliuniapp微信小程序投票系统实战课程 (SpringBoot2vue3.2element plus ) ( 火爆连载更新中... )共计21条视频…

工业异常检测AnomalyGPT-Demo试跑

写在前面&#xff1a;如果你有大的cpu和gpu可以使用&#xff0c;直接根据官方的安装说明就可以&#xff0c;如果没有&#xff0c;可以点进来试着看一下我个人的安装经验。 一、试跑环境 NVIDIA4090显卡24g,cpu内存33G&#xff0c;交换空间8g,操作系统ubuntu22.04(试跑过程cpu…