阻塞队列+定时器+常见的锁策略

1)阻塞队列:是一个线程安全的队列,是可以保证线程安全的

1.1)如果当前队列为空,尝试出队列,进入阻塞状态,一直阻塞到队列里面的元素不为空

1.2)如果当前队列满了,尝试入队列,也会产生阻塞,一直阻塞到队列中的元素不为满为止

1.3)所以在Java的标准库中内置了一个BlockingQueue(是一个接口)这样的类来实现阻塞队列这样的功能,它的用法与普通的入队列和出队列很相似,没有取队首元素的操作;

1.4)Java.util.concurrent这个包里面包含了很多与多线程并发相关的组件操作,简称JUC

  BlockingQueue<String> blockingQueue=new LinkedBlockingQueue<>();//基于链表来实现,可以指定阻塞队列的大小blockingQueue.put("hello");String str=blockingQueue.take();

阻塞队列的知识点补充:

1)add方法和offer方法可以将指定的元素放到BlockingQueue里面,此时阻塞队列可以容纳,那么直接返回true,否则直接返回false,不会使阻塞队列阻塞

2)但是put方法也是将我们制定的元素存放到blockingQueue里面,如果说这个阻塞队列没有空间那么调用该方法的线程会阻塞等待

3)poll(time):取出BlockingQueue排在首位的元素,如果不能立即取出,那么会等到time规定的时间内取,规定时间到还没有取到那么直接返回null;

4)take():取走BlockingQueue里排在首位的对象,若BlockingQueue为空,阻断进入等待状态直到Blocking有新的对象被加入为止

5)BlockingQueue不接受null 元素,试图add、put 或offer 一个null 元素时,某些实现会抛出NullPointerException,null 被用作指示poll 操作失败的警戒值;

  BlockingQueue<String> queue1=new ArrayBlockingQueue<String>(10);BlockingQueue<String> queue2=new LinkedBlockingQueue<String>(10);BlockingQueue<String> queue3=new PriorityBlockingQueue<>(10);      

1)手动实现一个阻塞队列:

在实现循环队列的时候,有一个重要的问题,如何判断使空队列,还是满的队列?

1)head==tail来进行判断,这是并不靠谱的

由于一直进行插入元素,导致的head==tail,就说明是队列满了

由于一直进行删除元素,导致的head==tail,就说明此时队列是空的

所以我们这么做:size=0就是空,size==数组长度就是满,在这里,必须要加一个锁对象,给谁加锁就锁哪一个对象

2)数组实现队列,就是一个循环队列,我们用[head,tail)这个范围来表示数组的一个有效元素范围

3)当head或者tail到达数组元素的末尾之后,就需要从头开始,重新进行循环

进行入队列:就是把新的元素放到tail位置上面,并且让tail++(元素不满)

进行出队列:就是把随手元素取出来,让head++(元素不为空)

1)可以浪费一个格子,直接浪费,head==tail认为是空,head=tail+1认为是满

2)可以是用一个变量来进行记录元素的个数,size==0认为是空size==array.length认为是满

  public static void main(String[] args) {myqueue queue=new myqueue();//作为交易场所Thread t1=new Thread(){//搞一个这样的线程作为生产者public void run(){for(int i=0;i<1000;i++){try{queue.put(i);System.out.println("生产元素生产了"+i+"个");sleep(1000);/每秒钟生产一个元素}catch(InterruptedException e){e.printStackTrace();}}}};t1.start();Thread t2=new Thread() {//搞一个这样的线程作为消费者public void run() {while (true) {//频繁取队首元素int num = queue.take();System.out.println("消费元素为" + num);}}};t2.start();}
}
public class MyBlockingQueue {public int[] array=new int[10];public int tail=0;public int head=0;public int count=0;Object object1=new Object();Object object2=new Object();public void put(int data) throws InterruptedException {synchronized (Object.class){if(count==array.length){object1.wait();}array[tail]=data;tail++;count++;if(tail==array.length){tail=0;}}}public int take() throws InterruptedException {int result=0;synchronized (Object.class){if(count==0){object2.wait();}result=array[head];head++;count--;if(head==array.length){head=0;}object1.notify();}return result;}
}
数组实现队列,就是一个循环队列
入队列,就是把新的元素放到tail位置上,并且tail++;
出队列,就是把队首元素取出来,也就是说把head位置的元素返回回去,如果是引用数据类型,要手动置为空,并且head++;
class MyQueue{
//保存数据的本体Object object=new Object();private int []arr1;
//队首元素下标private int head=0;
//队尾元素下标private int tail=0;
//有效数据元素的个数private int count=0;MyQueue() {this.arr1 = new int[1000];}public void put(int data) {synchronized (object) {if (count == arr1.length) {
//此时队列中的值已经满了
//此时的条件,最好写成while因为有可能会出现第一个线程放入元素后,第二个线程又继续放,就有会放满的情况,使用while的目的是为了让wait唤醒之后,再次去判断一下条件是否成立;try{object.wait();}catch(InterruptedException e){e.printStackTrace();}}arr1[tail] = data;tail++;
//处于tail到达数组末尾的情况if (tail == arr1.length) {tail = 0;}
//上面的这个条件判定可以写成tail=tail%array.lengthcount++;
//我们put成功了,就可以进行唤醒take中的wait操作,因为此时队列一定是不为空的object.notify();}}public int take(){synchronized (object){if (count == 0) {
//head==tail有一个元素,count=0一个元素都没有try {object.wait();} catch (InterruptedException e) {e.printStackTrace();}}int ret = arr1[head];head++;if (head == arr1.length) {head = 0;}
//我们take成功了,就可以唤醒put中的wait,因为此时队列一定不为满count--;object.notify();return ret;}}} 

 

1)要是想要这个队列支持线程安全,一定要保证在多线程环境下面调用这里面的put和take是没有任何问题的;

1.1)看了这里面的代码之后,put和take里面的每一行代码都是在操作公共的变量可以给整个方法来进行加锁或者是通过同步代码块的方式,指定this来加锁,或者指定一个专门的锁对象,锁这个对象和调用wait都是这个对象,所以说要是想保证线程安全,就需要进行使用synchronized来将若干个非原子性的操作,打包成原子性的操作

如果说要是想精准唤醒某一个线程,就需要使用不同的锁对象:

1)要是想唤醒t1,我们就必须o1.notify(),让t1进行o1.wait()

2)要是想唤醒t2,我们就必须o2.notify(),让t2进行o2.wait()

2)要想实现阻塞效果,我们就需要搭配对象等待集来进行使用

2.1)我们使用哪一个对象来进行加锁,就需要使用哪一个对象来进行wait操作,如果是针对this加锁就使用this.wait();

2.2)对于put操作来说,阻塞条件就是队列为满

2.3)对于take操作来说,阻塞条件就是队列为空

2.4)put中的wait要靠take来进行唤醒,条件是队列为满,只要队列不为满,只要我们take成功取走了一个元素,队列不就不为满了吗,就可以唤醒了

2.5)take中的wait要靠put来进行唤醒,条件是队列为空,只要我们的队列不为空,也就是说只要我们put成功放进去了一个元素,队列不就不为空了吗,就可以唤醒了

2.6)当前代码中,put操作和take操作两种操作不会同时wait,等待条件是截然不同的

注意:tail=tail%array.length这种写法十分的不建议

1)这种写法非常的不直观

2)取%操作,这一种操作对于计算机来说的开销是非常大的,相当于除法操作,比较操作就是一个跳转指令;既不利于提高开发效率,也不能提高运行效率

1)生产者生产元素的速度是小于消费者消费的速度

2)put操作和take操作有可能都会出现阻塞的情况,但是此时由于这两个代码中的阻塞条件是对立的,因此我们两边的wait不会同时触发

put操作就会唤醒take的阻塞,put操作就破坏了take的阻塞条件

take操作就会唤醒put的阻塞,take操作也就破坏了put的阻塞条件

3)下面的if操作最好换成while操作,如果是多个线程出现阻塞等待的时候,万一同时唤醒了多个线程,就很有可能出现,第一个线程放入元素,第二个线程又放的时候,就会出现满的情况,所以我们使用while就是为了让wait被唤醒之后,再次确定一下条件是否成立

4)如果说有人等待,那么notify是可以唤醒的,如果说没有等待,那么notify没有任何副作用 

二)关于生产者消费者模型的理解: 

生产者消费者模型,拿一个包饺子的例子来说

第一种方式:每一个人,擀完一个饺子一个包一个饺子,擀一个饺子包一个饺子,但是擀面杖只有一个,就会导致锁的冲突比较激烈,况且提高了门槛,我们只有先获取到这把锁才能进行擀饺子皮,其他人获取不到这把锁,就会阻塞等待,整体的效率并不会很高
第二种方式:一个人负责擀饺子,这个人擀出一堆皮,三个人负责取出这些皮,进行包饺子
1)生产者:擀皮的人;
2)消费者:包饺子的人;
3)交易场所:盖帘(放饺子皮的盖帘);

擀饺子皮的人就是饺子皮的生产者,要进行源源不断地生成饺子皮

包饺子的人就是及饺子皮的消费者,我们要不断地进行使用和消耗饺子皮

上述模型中一般生产者也只有一个,盖帘是交易场所,消费者确实有很多个

在计算机中,生产者就是一组线程,消费者是另一组线程,阻塞队列就是生产者消费者模型中的交易场所

所以说在我们平时写代码的过程中,代码一定要高内聚,低耦合

高内聚的意思就是说:希望不同模块之间,联系要尽量的少, 所以说我们使用生产者消费者模型就可以降低这里面的耦合

最大的用处解耦合:写了两个代码,一个代码中的两个代码块的关联关系很复杂,这样耦合就比较高,两个模块的关联关系尽量小,简单,整体的代码是可以相互理解的,耦合比较低,

1)例如A要传输一定的数据给B,如果直接传输,此时就要求,要么是A向B传输数据,要么是B向A拉取数据,都是需要A和B进行相互交互的,A和B之间存在着一些关联关系,如果B挂了,A也就会有太大的影响;

2)在我们开发A代码的时候就必须充分了解B提供的一些接口,开发B代码的时候要充分了解A是怎么调用的;

3)未来如果需要进行扩展,扩展也搞一个C,让A也给C传输数据,这个改动就可能比较复杂,因为本来是A和B进行传输的,多了一个C,那么就是A想C传输数据,或者是C和B来向A拉取数据,改动比较复杂,就认为A和B的耦合比较高,B挂了,对A没啥影响

1)A把数据写到队列里面,B再从队列里面取出元素进行消费,

2)A不知道数据要发送给谁,只需要向队列中添加元素,B不知道这个数据是谁发送过来的,只需要从队列中取元素即可

3)如果在后面需要进行扩展(再来C),也不需要直接从A要元素,只需要在队列中取出元素即可,这个阻塞队列就好似于变成了中转站一样的东西;
4)这样我们就做到了,让生产者和消费者可以不知道他们彼此之间是谁,这个数据是谁生产的,是谁消费的,都不重要,能生产,能消费就可以了,这样还是我们的系统变得更加灵活,可以随意替换A,B,C的任意一个模块,修改更方便,耦合耕地,让代码程序的维护性变得更高;

2)销峰填谷:
我们来举一个三峡大坝的例子
汛期:如果没有大坝,那么到了雨季,那么下游的水就会很大,就会造成水灾可能会发生灾难
罕期:如果没有大坝,那么下游的水就很少,有可能就会发生旱灾
于是就有了大坝:
1)汛期:关闸蓄水,并不是全部关了,让水按照一定的速率向下流,有节奏,按照一定的速率来进行放水,避免突然一波把下游带走(当突然下暴雨的时候);
2)罕期:开闸放水,让水也按照一定的速率向下流,避免下游太缺水,避免造成旱灾;

3)首先我们让消费者消费得快一些,让生产者生产的慢一些,此时我们会看到,消费者线程会阻塞等待,每当有新的生产者的元素时,消费者才会执行;

4)但是如果消费者消费得慢一些,让生产者生产的快一些,就会出现,一开始大量的迸发出生产元素,等到生产了100多个才开始消费;(代码是第二种情况

1)互联网上面过来的请求数量,是多还是少,是不可控的;突然来了大量请求,如果没有入口服务器,这些什么商家服务器,直播服务器就有可能会挂掉,操作数据库,效率比较低,况且需要的系统资源可能会更多,如果主机的硬件不够,程序就有可能直接挂了,咱们的入口服务器一般是不会垮掉的,因为入口服务器是不会不会处理数据请求的

2)通过一个队列来进行缓存请求,建立一个生产者消费者模型,此时即使网络这边过来一大波请求,这些请求只是冲击了队列服务器,对于后续的业务服务器,仍然是按照固定的速率来消费数据,阻塞队列是没有什么计算量的,就单纯的存个数据,就能抗住更大的压力

3)实际上,网管是不可以直接与各个服务器进行进行相连的,通过一个队列来实现生产者消费者模型

1)现在即使说现在网络上面过来了一大波请求,此时这些请求指示冲击了队列服务器,但是对于后面的业务服务器,任然是以固定的速率来进行消费数据,如果互联网这边的请求少了,后面的这些服务器也不会闲着,就会把之前队列积压的数据,来取出来进行处理

2)这里此时的请求的压力直接给到了阻塞队列这里面,此时针对我们的队列的请求是暴涨的,但是我们的阻塞队列没有做过多的计算,没啥计算量,就是单纯的存储一个数据,它是可以承受住一定的压力的;

咱们在实际开发中使用到的阻塞队列并不是一个简单的数据结构,而是一组专门的服务器程序,它所提供的功能也并不仅仅是阻塞队列的功能,还会在这上面的队列中增加新的功能,比如说对于数据持久化存储,支持多个数据通道,支持多节点容灾冗余备份,支持管理面板,方便于配置参数,这样的队列又起了一个新的名字,叫做消息队列,但是本质上还是阻塞队列的功能,kafak,mq就是业界常见使用的消息队列

 

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

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

相关文章

【vue会员管理系统】篇六之退出系统功能

一、效果图 点击之后跳转到登陆界面 二、实现步骤 2.1Easy Mock新增接口 打开Easy Mock新建接口 方法:post URL:user/logout 描述&#xff1a;退出系统 2.2新增api 在api/login.js下添加以下代码 export function logout(token) {return request({url: /user/logout,method:…

2023双十一:实体门店闯入,第二战场全面开战

“闺女&#xff0c;吃饺子了吗&#xff1f;”11月8日&#xff0c;立冬&#xff0c;忙碌一天的陈曦回家路上接到母亲电话&#xff0c;才想起来家里冷冻水饺没了&#xff0c;又不想再去超市&#xff0c;直接打开美团买菜买了两袋&#xff0c;回家就煮了吃。当然&#xff0c;最终她…

vr航空博物馆综合展馆趣味VR科普体验

第十期广州科普开放日 10月28日周六上午九点半&#xff0c;广州卓远VR科普基地再次迎来一批前来体验的亲子家庭&#xff0c;陆续到达的市民朋友让整个基地都热闹了起来&#xff0c;他们在这里开启了一场别开生面的VR科普体验。 一期一会&#xff0c;趣味VR科普 10月广州科普开放…

力扣21:合并两个有序链表

力扣21&#xff1a;合并两个有序链表 **题目描述&#xff1a;**将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 示例 1&#xff1a; 输入&#xff1a;l1 [1,2,4], l2 [1,3,4] 输出&#xff1a;[1,1,2,3,4,4] 示例 2&…

【LeetCode: 54. 螺旋矩阵 | 模拟】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

【Kurbernetes资源管理】声明式资源管理+配置清单文件详解(附实例)

声明式 一、声明式资源管理方式1.1 简介1.2 基本语法1.3 子命令详解1.3.1 获取资源配置清单1.3.2 创建/更新资源补充&#xff1a;creat和apply的区别 1.3.3 删除资源----- delete1.3.4 编辑资源配置 -----edit1.3.5 获取资源的解释-----explain 二、资源清单格式详解2.1 yaml语…

字形变换-头歌

将一个给定字符串 s 根据给定的行数 numRows &#xff0c;以从上往下、从左到右进行Z字形排列。之后&#xff0c;你的输出需要从左往右逐行读取&#xff0c;产生出一个新的字符串 示例 : 输入: s "QAZWSXEDCRFVTG"&#xff0c;numRows 4 输出:"QETAXDVGZSCFWR&…

SpringBoot 学习笔记(四) - 原理篇

一、自动配置 1.1 bean加载方式 bean的加载方式1 - xml方式声明bean 导入依赖&#xff1a; <dependencies><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.3.9</ver…

【Docker】设置容器系统字符集zh_CN.UTF-8退出失效:关于Docker容器配置环境变量,再次进入失效问题

设置容器系统字符集zh_CN.UTF-8退出失效&#xff1a;关于Docker容器配置环境变量&#xff0c;再次进入失效问题 修改正在运行的Docker容器内的字符集: 先进入Docker容器&#xff1a;docker exec -it 容器ID /bin/bash查看是否支持中文字符集&#xff1a;locale -a | grep zh&a…

蓝桥杯:分数

题目 思路 等比数列求和&#xff0c;手算然后输出 代码&#xff08;已过&#xff09; #include <iostream> using namespace std; int main() {// 请在此输入您的代码int a1024*1024-1;int b1024*512;cout<<a<<"/"<<b;return 0; }

数据分析实战 | 线性回归——女性身高与体重数据分析

目录 一、数据集及分析对象 二、目的及分析任务 三、方法及工具 四、数据读入 五、数据理解 六、数据准备 七、模型训练 八、模型评价 九、模型调参 十、模型预测 实现回归分析类算法的Python第三方工具包比较常用的有statsmodels、statistics、scikit-learn等&#…

Python+reuqests自动化接口测试

1.最近自己在摸索Pythonreuqests自动化接口测试&#xff0c;要实现某个功能&#xff0c;首先自己得有清晰的逻辑思路&#xff01;这样效率才会很快&#xff01; 思路--1.通过python读取Excel中的接口用例&#xff0c;2.通过python的函数调用&#xff0c;get/Post 进行测试&…

操作系统·处理机调度死锁

3.1 处理机调度概述 3.1.1 处理机调度概述 高级调度 (High level Scheduling)决定把外存上哪些作业调入内存、创建进程、分配资源。高级调度又称作业调度、长程调度或宏观调度。只在批处理系统中有高级调度。 中级调度 (Middle level Scheduling)完成进程的部分或全部在内、…

倍福CX9020 Windows CE6.0安装中文字库方法(附字库文件)

应用背景介绍 倍福的EPC产品有些是附带Windows CE系统的&#xff0c;例如CX9020&#xff0c;而且多数系统都是英文的&#xff0c;而且没有附带中文的字库&#xff0c;如果想要在PLC HMI中使用中文进行显示就无法实现&#xff0c;经常有工程师在电脑上编好程序和界面以后测试没…

2023年11月PHP测试覆盖率解决方案

【题记&#xff1a;最近进行了ExcelBDD PHP版的开发&#xff0c;查阅了大量资料&#xff0c;发现PHP测试覆盖率解决方案存在不同的历史版本&#xff0c;让我花费了蛮多时间&#xff0c;为了避免后人浪费时间&#xff0c;整理本文&#xff0c;而且网上没有给出Azure DevOps里面P…

移动医疗科技:开发互联网医院系统源码

在这个数字化时代&#xff0c;互联网医院系统成为了提供便捷、高效医疗服务的重要手段。本文将介绍利用移动医疗科技开发互联网医院系统的源码&#xff0c;为医疗行业的数字化转型提供有力支持。 智慧医疗、互联网医院这一类平台可以通过线上的形式进行部分医疗服务&#xff…

王道数据结构课后代码题p150 15.设有一棵满二叉树(所有结点值均不同),已知其先序序列为 pre,设计一个算法求其后序序列post。(c语言代码实现)

对一般二叉树&#xff0c;仅根据先序或后序序列&#xff0c;不能确定另一个遍历序列。但对满二叉树&#xff0c;任意一个结点的左、右子树均含有相等的结点数&#xff0c;同时&#xff0c;先序序列的第一个结点作为后序序列的最后个结点。 本题代码如下 void pretopost(char …

浅谈泛在电力物联网在智能配电系统应用

贾丽丽 安科瑞电气股份有限公司 上海嘉定 201801 摘要&#xff1a;在社会经济和科学技术不断发展中&#xff0c;配电网实现了角色转变&#xff0c;传统的单向供电服务形式已经被双向能流服务形式取代&#xff0c;社会多样化的用电需求也得以有效满足。随着物联网技术的发展&am…

基于SSM的社区生鲜电商平台

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;Vue 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#xff1a;是 目录…

python爬虫怎么翻页 ?

首先&#xff0c;你需要安装相关的库。在你的命令行窗口中&#xff0c;输入以下命令来安装所需的库&#xff1a; pip install requests beautifulsoup4然后&#xff0c;你可以使用以下代码来爬取网页内容并翻页&#xff1a; package mainimport ("fmt""net/htt…