【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.mzph.cn/news/598112.shtml

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

相关文章

web前端之web前端

MENU web前端之原生实现分时函数、分时间段渲染页面&#xff0c;而不是等到所有操作都执行结束才渲染页面、提高用户体验web前端之原生实现树形目录结构、一维数组生成多维数组、无限级菜单目录 web前端之原生实现分时函数、分时间段渲染页面&#xff0c;而不是等到所有操作都执…

JS 文件导出,jszip多文件压缩导出

文章目录 JS 文件导出&#xff0c;jszip压缩导出1、单个文件导出2、压缩导出3、axios请求通过地址获取Blob源数据 JS 文件导出&#xff0c;jszip压缩导出 1、单个文件导出 let urlhttp://~.png // 可直接访问地址 // 当源数据为blob时 // url window.URL.createObjectURL(bl…

已经在datagrip或navicate等数据库管理工具连接上了mysql8.0,但是现在忘记了,怎么快速重置密码

直接在datagrip的查询终端中&#xff0c;执行这条sql&#xff0c;其中123456为你的新密码。 ALTER USER rootlocalhost IDENTIFIED BY 123456;

新手可理解的PyTorch线性层解析:神经网络的构建基石

目录 torch.nn子模块Linear Layers详解 nn.Identity Identity 类描述 Identity 类的功能和作用 Identity 类的参数 形状 示例代码 nn.Linear Linear 类描述 Linear 类的功能和作用 Linear 类的参数 形状 变量 示例代码 nn.Bilinear Bilinear 类的功能和作用 B…

国家信息安全水平等级考试NISP二级题目卷⑥(包含答案)

国家信息安全水平等级考试NISP二级题目卷&#xff08;六&#xff09; 国家信息安全水平等级考试NISP二级题目卷&#xff08;六&#xff09;需要报考咨询可以私信博主&#xff01; 前言&#xff1a; 国家信息安全水平考试(NISP)二级&#xff0c;被称为校园版”CISP”,由中国信息…

用友U8 Cloud smartweb2.RPC.d XML外部实体注入漏洞

产品介绍 用友U8cloud是用友推出的新一代云ERP&#xff0c;主要聚焦成长型、创新型、集团型企业&#xff0c;提供企业级云ERP整体解决方案。它包含ERP的各项应用&#xff0c;包括iUAP、财务会计、iUFO cloud、供应链与质量管理、人力资源、生产制造、管理会计、资产管理&#…

基于gamma矫正的照片亮度调整(python和opencv实现)

import cv2 import numpy as npdef adjust_gamma(image, gamma1.0):invGamma 1.0 / gammatable np.array([((i / 255.0) ** invGamma) * 255 for i in np.arange(0, 256)]).astype("uint8")return cv2.LUT(image, table)# 读取图像 original cv2.imread("tes…

影视仓最新配置接口2024tvbox源配置地址

影视仓是在TVBox开源代码基础上开发的优质版本&#xff0c;安装后需要配置接口才能正常使用。影视仓"内置版"是开发者做的资源内置化修改版本&#xff0c;不用自行设置接口&#xff0c;安装后即可使用。 影视仓的接口配置方法与TVBOX一样&#xff0c;区别在于影视仓…

Winform工具箱控件MenuStrip

MenuStrip是菜单栏 ComboBox是下拉框 TextBox是文本框 DataGridView是数据表 TextBox是文本框&#xff0c;大小可以调节&#xff0c;可以是单行&#xff0c;也可以是多行&#xff0c;通过右上角的小三角可以修改。 这个文本在编辑的时候可以在属性的Text中点击右边的小三角来换…

卷积神经网络|导入图片

在学习卷积神经网络时&#xff0c;我们通常使用的就是公开的数据集&#xff0c;这里&#xff0c;我们不使用公开数据集&#xff0c;直接导入自己的图片数据&#xff0c;下面&#xff0c;就简单写个程序实现批量图片的导入。 import osfrom PIL import Imageimport numpy as np…

【docker】使用 Dockerfile 构建镜像

一、什么是Dockerfile Dockerfile 是用于构建 Docker 镜像的文本文件。它包含了一系列的指令&#xff0c;用于描述如何构建镜像的步骤和配置。 通过编写 Dockerfile&#xff0c;您可以定义镜像的基础环境、安装软件包、复制文件、设置环境变量等操作。Dockerfile 提供了一种可…

Prometheus实战篇:Prometheus监控redis

准备环境 docker-compose安装redis docker-compose.yaml version: 3 services:redis:image:redis:5container_name: rediscommand: redis-server --requirepass 123456 --maxmemory 512mbrestart: alwaysvolumes:- /data/redis/data: /dataport:- "6379:6379"dock…

Python基础(十七、函数进阶用法)

文章目录 一、函数的回顾二、函数的进阶用法1.多个返回值示例&#xff0c;获取验证码及用户名示例&#xff0c;获取用户信息 2.多种参数1.位置参数2.关键字参数3.缺省参数4.不定长参数 3.匿名函数函数作为参数传递lambda匿名函数(一行代码) 总结练习题目&#xff1a; 之前学习了…

JAVA版随机抽人

主函数 public class Main {public static void main(String[] args) {//这里存入数据String[] data {"土一","李二","张三","李四","乔冠宇","王五"};MyJFrame frame new MyJFrame(data);} }界面类 import j…

leetcode 动态规划(斐波那契数列、 爬楼梯、使用最小花费爬楼梯)

509. 斐波那契数 斐波那契数&#xff0c;通常用 F(n) 表示&#xff0c;形成的序列称为 斐波那契数列 。该数列由 0 和 1 开始&#xff0c;后面的每一项数字都是前面两项数字的和。也就是&#xff1a; F(0) 0&#xff0c;F(1) 1 F(n) F(n - 1) F(n - 2)&#xff0c;其中 n …

【React系列】Portals、Fragment

本文来自#React系列教程&#xff1a;https://mp.weixin.qq.com/mp/appmsgalbum?__bizMzg5MDAzNzkwNA&actiongetalbum&album_id1566025152667107329) Portals 某些情况下&#xff0c;我们希望渲染的内容独立于父组件&#xff0c;甚至是独立于当前挂载到的DOM元素中&am…

GPU连通域分析方法

第1章连通域分析方法 连通域分析方法用于提取图像中相似属性的区域&#xff0c;并给出区域的面积&#xff0c;位置等特征信息。分为两种&#xff0c;基于游程&#xff08;Runlength&#xff09;&#xff0c;和基于标记(Label)。 基于游程的方法&#xff0c;按照行对图像进行游…

3D Gaussian Splatting复现

最近3D Gaussian Splatting很火&#xff0c;网上有很多复现过程&#xff0c;大部分都是在Windows上的。Linux上配置环境会方便简单一点&#xff0c;这里记录一下我在Linux上复现的过程。 Windows下的环境配置和编译&#xff0c;建议看这个up主的视频配置&#xff0c;讲解的很细…

[算法与数据结构][python]:Python参数传递,“值传递”还是“引用传递“?

Python中的函数参数传递方式是“传对象引用”&#xff0c;可以理解为“值传递”和“引用传递”的混合体。 在Python中&#xff0c;所有的数据类型都是对象。如果函数参数是不可变对象&#xff08;如整数、字符串、元组&#xff09;&#xff0c;那么传递的就是对象的值&#xf…

695岛屿最大面积

题目 给定一个 row x col 的二维网格地图 grid &#xff0c;其中&#xff1a;grid[i][j] 1 表示陆地&#xff0c; grid[i][j] 0 表示水域。 网格中的格子 水平和垂直 方向相连&#xff08;对角线方向不相连&#xff09;。整个网格被水完全包围&#xff0c;但其中恰好有一个…