阻塞队列的安全实现,定时器的安全实现(面试易考),超详细

 一、💛

如何实现一个线程安全的阻塞队列

目前,当前代码是循环队列(还没有进行改动)

head和tail的判空判断满两种方法:

1.浪费一个格子,当前走到head的前一个位置,就认为队列满的

2.单独搞一个变量,去记录当前的元素个数  (下面的代码是使用的这个)

class MyBlockingQueue{private  String[] items=new String[1000];//队列的头private int head=0;//描述队列的下一个元素,也就是正常队列的队尾private int tail=0;//统计多少个数量public  int size=0;//插入public void put(String elem){if(size>=items.length){return ;}items[tail]=elem;tail++;if(tail>=items.length){     //这一步看我下面解析tail=0;}size++;}//取出队头元素public String take(){if(size==0){return null;}String elem=items[head];head++;if(head> items.length){     //这一步看我下面解析head=0;}size--;return elem;}
}

在上述代码中,

if(tail>=items.length){   
            tail=0;
        }这个代码和我们平时的循环队列是不是发现有点不一样,我们平时的选择是下面这个

tail=(tail+1)%item.length 但是你实际一想,他不就是把越界的给恢复到从0开始吗,所以结果一致,而且 ,我们写代码是两个目的:

1.让写出来的代码,开发效率高(好去理解,好修改)

2.让写出的代码,运行效率高(执行速度快)  另外再补充一个小知识。cpu运算加,减,位运算,是比乘除更快一些的,乘除假如是针对(2的幂)的乘除运算,可以被优化成为位运算。

二、💙 

改造(上面写的战损版)阻塞队列:

1.线程安全问题:加锁解决(直接无脑加锁(打包成原子),修改变量(count++这个类型的)的地方太多了)

2.内存可见性问题,一个线程在改,另一个线程读,内存可见性是否能够观察到修改

3.阻塞等待,wait,notify(让他等待,而不是直接返回值啥的)(插入的时候,假如说满就让他等待一会,直到你出队列,空的时候就等一会,直到你插入队列。)

class MyBlockingQueue{private  String[] items=new String[1000];//队列的头private  volatile int head=0;//描述队列的下一个元素,也就是正常队列的队尾private volatile int tail=0;//统计多少个数量public  volatile int size=0;//插入public void put(String elem) throws InterruptedException {synchronized (this) {while(size >= items.length) {this.wait();               //1号(满了就阻塞等待 4号会来唤醒1号)}items[tail] = elem;tail++;if (tail >= items.length) {tail = 0;}size++;this.notify();                //2号(2号来唤醒,3号)}}//取出队头元素public String take() throws InterruptedException {synchronized (this) {while(size == 0) {this.wait();            //3号(空了,就阻塞等待,等2号)}String elem = items[head];head++;if (head > items.length) {head = 0;}size--;this.notify();              //4号(4唤醒1号return elem;}}
}

使用wait时候,建议搭配while使用

原因:put操作之后因为队列满,进入wait阻塞了,但是过一会wait被唤醒,唤醒的时候,队列一定就是非满状态吗,万一唤醒还是满着的,此时继续执行,不就会把之前的元素覆盖了吗。

wait是一定被notify唤醒吗?

在当前代码中,如果是interrupet唤醒会报异常,方法就会结束了,但是也就不会导致刚才说的覆盖已有元素问题。但是⚠️ 假如我拿try catch阁下该如何应对捏?——一旦是被interrupt唤醒,catch执行完毕,继续执行,也就触发了“覆盖元素”的逻辑(我们这个抛异常操作是碰巧蒙对了)

所以解决方法,拿while更加万无一失

最终下图的代码:也会理解前面的一些东西

import java.util.Scanner;
import java.util.concurrent.*;class MyBlockingQueue{private  String[] items=new String[1000];//队列的头private  volatile int head=0;//描述队列的下一个元素,也就是正常队列的队尾private volatile int tail=0;//统计多少个数量public  volatile int size=0;//插入public void put(String elem) throws InterruptedException {synchronized (this) {if(size >= items.length) {this.wait();               //1号}items[tail] = elem;tail++;if (tail >= items.length) {tail = 0;}size++;this.notify();                //2号}}//取出队头元素public String take() throws InterruptedException {synchronized (this) {if (size == 0) {this.wait();            //3号}String elem = items[head];head++;if (head > items.length) {head = 0;}size--;this.notify();              //4号return elem;}}
}
public  class  Demo {public static void main(String[] args)  {MyBlockingQueue myBlockingQueue=new MyBlockingQueue();Thread t1=new Thread(()->{int count =0;while (true){try {myBlockingQueue.put(String.valueOf(count));} catch (InterruptedException e) {e.printStackTrace();}System.out.println("生产了元素"+count);count++;}});Thread t2=new Thread(()->{while(true) {Integer n = null;try {n = Integer.valueOf(myBlockingQueue.take());} catch (InterruptedException e) {e.printStackTrace();}System.out.println("消费者" + n);try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}});t1.start();t2.start();}
}

 和前面的生产者消费者模型,但是我想说的是,控制节奏,t1会快速执行到1000,后按照t2的sleep的节奏一点一点生产。


三、💜

定时器:日常开发中,常用到的组件(前后端都要用的定时器,类似于一个闹钟)

Timer类(util包)

public class Demo1 {public static void main(String[] args) {Timer timer=new Timer();timer.schedule(new TimerTask() {             //给Timer中注册的任务,不是在调用@Override                             schedule的线程中执行的,而是通过Timer     public void run() {                 内部的线程来执行的   System.out.println("hello");}},3000);                            //过了这些时间才开始执行的}
}

注意:schedule可以设置多个任务,任务的完成先后是根据等待时间的

四、💚

如何我们自己实现一个定时器(闹钟):首先先要把任务描述出来,再用数据结构来把多个任务组织起来。

1.创建一个TimerTask这样的类,表示一个任务,该任务需包含两方面,任务的内容,任务的实际执行时间->通过时间戳表示:在schedule的时候,先获取当前系统时间,在这个基础上,加上(延时执行),得到了真实要执行这个任务的时间

2.使用一定的数据结构,把多个TimerTask组织起来

思路1(看看行不行):咱们用(链表/数组)组织TimerTask,如果任务特别多,如何确定哪个任务,何时能够执行呢?这时候就需要搞一个线程,不断的去遍历List,进行遍历,看着里面的每个元素,是否到了时间,时间到就执行,不到就跳过下一个。

回答:不行,这个思路可以,但是非常不好,假如任务执行时间都特别长,你这段时间一直都在遍历,扫描,会很消耗cpu(还没做事)。

思路2(正解):

1.并不需要扫描全部的任务,我们其实只需要关注一个,就是最早,时间最靠前的任务即可,最前面的任务假如还没到达的话,其他的任务更不会到达!!!

遍历一个->只关注一个,那么也就是说最好能选出最大,最小这种的数据结构(脑子里第一个肯定就是——堆(优先级队列))。

2.针对这个任务扫描,也不用一遍一遍一直重复执行,我们获取完队首时间(最小的任务时间)和当前时间做差值,在这个差值到达之前,可以让他休眠或者等待,这样不会进行重复的扫描,大幅度降低了扫描的次数->这样也不消耗cpu,(此处提高效率,不是缩短执行时间(定时器,时间是固定的),减少了资源利用率,避免了不必要的浪费)

战损没有改良的简版。

class MyTimertask{private long time;                 //任务什么时候执行,毫秒级的时间戳private Runnable runnable;         //具体的任务public MyTimertask(Runnable runnable,long delay){time=System.currentTimeMillis()+delay;this.runnable=runnable;}public long getTime() {return time;}public Runnable getRunnable() {return runnable;}
}
class MyTimer {//使用优先级队列private PriorityQueue<MyTimertask> queue = new PriorityQueue<>();//定时器核心方法public void schedule(Runnable runnable, long delay) {MyTimertask task = new MyTimertask(runnable, delay); queue.offer(task);}//定时器还要搞个扫描方法,内部的线程。一方面负责监控队首元素是否到点了,是否应该执行,// 一方面当任务到了之后,就要调用这里的Runable的run方法执行任务public MyTimer() {Thread t = new Thread(() -> {while (true) {//队列为空,此时不可以取元素if (queue.isEmpty()) {continue;}MyTimertask Task = queue.peek();long curTime = System.currentTimeMillis();if (curTime >= Task.getTime()) {queue.poll();Task.getRunnable().run();} else {try {Thread.sleep(Task.getTime() - curTime);} catch (InterruptedException e) {e.printStackTrace();}}}});t.start();}
}

当前战损版的问题:

1.线程不安全,PriorityQueue<MyTimeTask> queue=new PriorityQueue<>();

这个集合类不是线程安全的,他这个既在线程中使用,又在扫描线程中使用,给针对queue的操作进行加锁。(直接无脑暴力加锁)

2.扫描线程中的sleep是否合理呢?

(1)sleep阻塞后,不会释放锁(休眠的时候,最短的时间都还很长,这么大段时间无法添加任务)影响其他线程执行添加

(2)slepp在休眠过程中,不方便提前中断,(虽然可以用interrupt来中断,单用interrupt意味线程应该结束了)

 假设当前最近的任务是14:00执行,当前时刻是13:00(sleep一小时),此时新增一个新的任务,新任务13:30要执行(成为了最早的任务),所以为了中断,应该使用wait

(3)我们设置的时候,需要把可比较写上,也就是说Comparable/Comparaor,也要知道,是什么需要比较呢,时间(顺序记不清无所谓,试一下就OK了)。

import java.util.PriorityQueue;//创建一个类,用来描述定时器中的一个任务
class MyTimertask implements Comparable<MyTimertask>{private long time;                 //任务什么时候执行,毫秒级的时间戳private Runnable runnable;         //具体的任务public MyTimertask(Runnable runnable,long delay){time=System.currentTimeMillis()+delay;this.runnable=runnable;}public long getTime() {return time;}public Runnable getRunnable() {return runnable;}@Overridepublic int compareTo(MyTimertask o) {return  (int)(this.time-o.time);}
}
class MyTimer {//使用优先级队列private PriorityQueue<MyTimertask> queue = new PriorityQueue<>();//定时器核心方法public void schedule(Runnable runnable, long delay) {synchronized (this) {MyTimertask task = new MyTimertask(runnable, delay);queue.offer(task);                     //新来一个任务,就需要把线程唤醒this.notify();}}//定时器还要搞个扫描方法,内部的线程。一方面负责监控队首元素是否到点了,是否应该执行,// 一方面当任务到了之后,就要调用这里的Runable的run方法执行任务public MyTimer() {Thread t = new Thread(() -> {while (true) {synchronized (this) {//队列为空,此时不可以取元素while (queue.isEmpty()) {try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}MyTimertask Task = queue.peek();               //任务的时间long curTime = System.currentTimeMillis();     //当前的系统时间if (curTime >= Task.getTime()) {               //系统时间queue.poll();               14:01>任务时间14:00 ——执行任务Task.getRunnable().run();} else {try {this.wait(Task.getTime() - curTime);} catch (InterruptedException e) {e.printStackTrace();}}}}});t.start();}
}
public class Demo3 {public static void main(String[] args) {MyTimer myTimer = new MyTimer();myTimer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("hello");}}, 3000);myTimer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("god");}}, 2000);myTimer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("good");}}, 1000);}}

这应该是自从数据结构以来,我们学过最难的了,所以大家多敲几遍理解理解

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

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

相关文章

系统架构设计专业技能 · 网络规划与设计(三)【系统架构设计师】

系列文章目录 系统架构设计专业技能 网络规划与设计&#xff08;三&#xff09;【系统架构设计师】 系统架构设计专业技能 系统安全分析与设计&#xff08;四&#xff09;【系统架构设计师】 系统架构设计高级技能 软件架构设计&#xff08;一&#xff09;【系统架构设计师…

手把手带你跑通网站上线全流程(一个简单的HTML和Python服务端完整上线流程)

我将向你介绍如何将一个网站部署到公网&#xff0c;包含完整流程。 前端静态网站 静态网站文件 首先需要准备一个简单的网页文件用于展示页面 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name&quo…

2023深圳杯A题完整代码模型

已更新深圳杯A题全部版本&#xff0c;文末获取&#xff01; 摘要 现代社会&#xff0c;随着生活方式的变化和工作压力的增大&#xff0c;慢性非传染性疾病日益成为威胁公众健康的主要问题。心脑血管疾病、糖尿病、恶性肿瘤及慢性阻塞性肺病等慢性病的发病率呈现出上升趋势。为…

23款奔驰AMG GT50更换原厂运动排气系统,战斗感立马提升了

改装运动排气&#xff0c;原车中控的按键组也是需要更换的。与原车按键相比&#xff0c;只是多了一个排气的控制按键&#xff0c;也正是这个按键&#xff0c;能让车辆可静可怒&#xff0c;安静与怒吼就在一键之间。

linux_常用命令

一、日常使用命令/常用快捷键命令 开关机命令 1、shutdown –h now&#xff1a;立刻进行关机 2、shutdown –r now&#xff1a;现在重新启动计算机 3、reboot&#xff1a;现在重新启动计算机 4、su -&#xff1a;切换用户&#xff1b;passwd&#xff1a;修改用户密码 5、logou…

ChatGLM2-6B在Windows下的微调

ChatGLM2-6B在Windows下的微调 零、重要参考资料 1、ChatGLM2-6B! 我跑通啦&#xff01;本地部署微调&#xff08;windows系统&#xff09;&#xff1a;这是最关键的一篇文章&#xff0c;提供了Windows下的脚本 2、LangChain ChatGLM2-6B 搭建个人专属知识库&#xff1a;提供…

计算机网络—TCP

这里写目录标题 TCP头格式有哪些为什么需要TCP&#xff0c;TCP工作在哪什么是TCP什么是TCP连接如何确定一个TCP连接TCP和UDP的区别&#xff0c;以及场景TCP和UDP能共用一个端口&#xff1f;TCP的建立TCP三次握手过程为什么是三次握手、不是两次、四次why每次建立连接&#xff0…

2023年游戏买量能怎么玩?

疫情过后&#xff0c;一地鸡毛。游戏行业的日子也不好过。来看看移动游戏收入&#xff1a;2022年&#xff0c;移动游戏收入达到920亿美元&#xff0c;同比下降6.4%。这告诉我们&#xff0c;2022年对移动游戏市场来说是一个小挫折。 但不管是下挫还是上升&#xff0c;移动游戏市…

python技术栈 之 单元测试中mock的使用

一、什么是mock&#xff1f; mock测试就是在测试过程中&#xff0c;对于某些不容易构造或者不容易获取的对象&#xff0c;用一个虚拟的对象来创建以便测试的测试方法。 二、mock的作用 特别是开发过程中上下游未完成的工序导致当前无法测试&#xff0c;需要虚拟某些特定对象…

机器学习深度学习——RNN的从零开始实现与简洁实现

&#x1f468;‍&#x1f393;作者简介&#xff1a;一位即将上大四&#xff0c;正专攻机器学习的保研er &#x1f30c;上期文章&#xff1a;机器学习&&深度学习——循环神经网络RNN &#x1f4da;订阅专栏&#xff1a;机器学习&&深度学习 希望文章对你们有所帮…

React实现关键字高亮

先看效果&#xff1a; 实现很简单通过以下这个函数&#xff1a; highLight (text, keyword ) > {return text.split(keyword).flatMap(str > [<span style{{ color: red, fontWeight: bold }}>{keyword}</span>, str]).slice(1);}展示某段文本时调用该函数…

完成图像反差处理

bmp图像的前54字节为图像头&#xff0c;第19个字节开始4字节为图像宽&#xff0c;第23字节开始4字节为图像高&#xff0c;图像大小为&#xff1a;972*720*3542099574&#xff0c;为宽*高*像素点头&#xff0c;如下&#xff1a; 图像的反差处理

Android系统-ServiceManager2

目录 引言&#xff1a; 获取ServiceManager 流程图 注册系统服务 获取系统服务 引言&#xff1a; 注册或使用服务之前&#xff0c;需要通过ServiceManager这个DNS来找到对应的服务。那怎么找到ServiceManager呢&#xff1f; 怎么注册系统服务&#xff1f; 怎么获取系统…

Golang 函数定义及使用

文章目录 一、函数定义格式二、函数定义及使用 一、函数定义格式 //func: 函数定义关键字 //function_name&#xff1a;函数名称 //parameter_List: 函数参数列表&#xff0c;多个时使用逗号拆分 //return_types&#xff1a;函数返回类型&#xff0c;返回多个值时使用逗号拆分…

SpringBoot 2.1.7.RELEASE + Activiti 5.18.0 喂饭级练习手册

环境准备 win10 eclipse 2023-03 eclipse Activiti插件 Mysql 5.x Activiti的作用等不再赘叙&#xff0c;直接上代码和细节 POM <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId>…

web前端之CSS操作

文章目录 一、CSS操作1.1 html元素的style属性1.2 元素节点的style属性1.3 cssText属性 二、事件2.1 事件处理程序2.1.1 html事件2.1.2 DOM0事件&#xff08;适合单个事件&#xff09;2.1.3 DOM2事件&#xff08;适合多个事件&#xff09; 2.2 事件之鼠标事件2.3 事件之Event事…

Python分享之 Spider

一、网络爬虫 网络爬虫又被称为网络蜘蛛&#xff0c;我们可以把互联网想象成一个蜘蛛网&#xff0c;每一个网站都是一个节点&#xff0c;我们可以使用一只蜘蛛去各个网页抓取我们想要的资源。举一个最简单的例子&#xff0c;你在百度和谷歌中输入‘Python&#xff0c;会有大量和…

选择最适合自己的笔记本

选择最适合自己的笔记本电脑 一、了解笔记本品牌一线品牌准一线品牌二线品牌三线品牌 二、笔记本入手渠道笔记本入手渠道 三、根据需求选择机型使用需求1.日常使用2.商务办公、财务3.轻度剪辑、ps4.代码5.创意设计6.游戏 四、笔记本电脑配置如何选1.cpu2.显卡&#xff08;GPU&a…

Vue响应式数据的原理

在 vue2 的响应式中&#xff0c;存在着添加属性、删除属性、以及通过下标修改数组&#xff0c;但页面不会自动更新的问题。而这些问题在 vue3 中都得以解决。 vue3 采用了 proxy 代理&#xff0c;用于拦截对象中任意属性的变化&#xff0c;包括&#xff1a;属性的读写、属性的…

UTONMOS:元宇宙在网络游戏领域得到充分运用

元宇宙到底是个啥&#xff1f;这个词大概意思应该就是人类能从真实世界进入一个虚拟世界体验另一种生活&#xff0c;这个虚拟的世界就叫“元宇宙”。 从科幻走入现实&#xff0c;元宇宙究竟有什么用途&#xff1f;它离我们到底还有多远&#xff1f;又将给我们的生活带来哪些变…