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

news2024/9/19 10:39:01

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.length
                count++;
//我们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.coloradmin.cn/o/1194139.html

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

相关文章

Ionic 组件 ion-item-divider ion-item-group ion-item-sliding ion-label ion-note

1 ion-item-divider Item dividers是块元素&#xff0c;可用于分隔列表中的items 。它们类似于列表标题&#xff0c;但它们不应该只放在列表的顶部&#xff0c;而应该放在items之间。 <ion-list><ion-item-group><ion-item-divider><ion-label> Secti…

javascript 操作mysql数据库

目录 一&#xff1a;Javascript访问MYSQL 二&#xff1a;JavaScript中操作Mysql数据库实例 一&#xff1a;Javascript访问MYSQL 1、下载MYSQL的ODBC连接 2、在JS中建立ODBC连接如下&#xff1a; var con new ActiveXObject("ADODB.Connection"); con.Connection…

Linux软件包(源码包和二进制包)

Linux下的软件包众多&#xff0c;且几乎都是经 GPL 授权、免费开源&#xff08;无偿公开源代码&#xff09;的。这意味着如果你具备修改软件源代码的能力&#xff0c;只要你愿意&#xff0c;可以随意修改。 GPL&#xff0c;全称 General Public License&#xff0c;中文名称“通…

jbase代码生成器(成型篇)

上一篇说到通用码表可以解决百分之八十的基础维护功能&#xff0c;剩下的百分二十的需要级联维护的界面可以用代码生成器生成代码&#xff0c;基于生成的代码拷贝再组装界面&#xff0c;来解决这百分之二十的工作量里的百分之八十工作量。 首先实现代码生成器 Class Jbase.Ma…

C语言实现给出一位数不大于7位的整型数字,取整数从右端开始的4~7位数字

完整代码&#xff1a; // 给出一位数不大于7位的整型数字&#xff0c;取整数从右端开始的4&#xff5e;7位数字 //就是一个数为abcdefg&#xff0c;取它从右端开始的4&#xff5e;7位数字&#xff0c;就为dcba //如果位数不足7位时&#xff0c;会在数字高位补0 //例如一个数为…

leetcode 62

leetcode 62 题目 解题思路 class Solution { public:int uniquePaths(int m, int n) {vector<vector<int>> f(m, vector<int>(n));for(int i0; i<m; i){f[i][0] 1;}for(int j0; j<n; j){f[0][j] 1;}for(int i1; i<m; i){for(int j1; j<n; j){…

如何记录血压的波动情况

import pandas as pd from plotnine import * import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [Microsoft YaHei] 记录时间(time)、收缩压(SBP)、舒张压(DBP)&#xff1a; df pd.DataFrame({ time: [2023-11-01 08:30, 2023-11-02 21:00, 2023-11-0…

【星海随笔】SDN neutron (一)

一、SDN的原理&#xff1a; 控制平面与数据平面分离&#xff1a;传统网络中&#xff0c;网络设备同时承担控制和数据转发功能&#xff0c;而SDN将这两个功能分离&#xff0c;使得网络控制集中在一个中心控制器上。 中心控制器&#xff1a;SDN架构中的中心控制器负责网络的全局…

【小黑送书—第五期】>>《MATLAB科学计算从入门到精通》

从代码到函数&#xff0c;从算法到实战&#xff0c;从问题到应用&#xff0c;由浅入深掌握科学计算方法&#xff0c;高效解决实际问题。 从代码到函数&#xff0c;掌握多种经典算法 跨越多个领域&#xff0c;精通各类科学计算 多种应用实例&#xff0c;高效解决实际问题 今天给…

【数据结构】Lambda

⭐ 作者&#xff1a;小胡_不糊涂 &#x1f331; 作者主页&#xff1a;小胡_不糊涂的个人主页 &#x1f4c0; 收录专栏&#xff1a;浅谈数据结构 &#x1f496; 持续更文&#xff0c;关注博主少走弯路&#xff0c;谢谢大家支持 &#x1f496; Lambda表达式 1. 背景1.1 语法1.2 函…

零基础必知的Python简介!

文章目录 前言1.Python 简介2.Python 发展历史3.Python 特点3.为什么是Python而不是其他语言&#xff1f;4.Python的种类总结关于Python技术储备一、Python所有方向的学习路线二、Python基础学习视频三、精品Python学习书籍四、Python工具包项目源码合集①Python工具包②Python…

sqli-bypass wp

sqli-bypass靶场 level 1 尝试注入点 1 ,1&#xff0c;1,1",1"" 》存在字符型单引号注入 id1and(1)-- >提示存在sql注入 bypass and、()、--都可能存在被屏蔽掉 尝试#代替-- id1and(1)%23 》 正常回显&#xff0c;说明–被屏蔽了&#xff0c;and&#xf…

相机标定:张正友标定原理

本文来自公众号“AI大道理” —————— 计算机视觉的源头是相机&#xff0c;因此我们有必要对相机有所了解。 原始相机拍摄的图像一般都会有所畸变&#xff0c;导致画面和实际观测的有所排查&#xff0c;为了让相机拍摄的图像和肉眼观察的一致&#xff0c;就需要进行相机标…

双网卡多网卡时win11如何设置网卡优先级

背景&#xff1a; 电脑需要同时使用多个网卡&#xff0c;一个用于被远程、另一个用于打开网页。 电脑打开网页时&#xff0c;走的是哪个网卡&#xff0c;是根据网卡优先级来的。 打开控制面板、网络和Internet、网络和共享中心 点击左侧 更改适配器设置。我这里可见两个网卡…

C语言—统计从键盘输入的一行英文句子的字符个数

流程图 代码 #include <stdio.h>int main() {int count0;printf("请输入英文字符&#xff0c;回车确认&#xff1a;");while (getchar()!\n){count count 1;}printf("共输入%d个字符\n", count);system("pause");return 0; }请输入英文字…

Git的安装,简介以及常用命令【超详细】

目录 一. Git简介 分布式特点 优缺点 Git 与 SVN 区别 二. Git安装 三. Git常用命令 四. Git的文件状态 1.文件状态 2.工作区域 一. Git简介 Git 是一个开源的分布式版本控制系统&#xff0c;可以有效、高速地处理从很小到非常大的项目版本管理。 也是Linus Torvalds…

粘性定位-最下面怎么出现留白

问题&#xff1a;出现了留白 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta name"viewport" content"widthdevice-width, initial-scale1.0" /><title>Document</title…

网格变形算法

网格变形 需求分析技术分析 需求分析 根据几何模型上的几个特征点&#xff0c;对几何模型进行变形。比如 技术分析 把几何模型使用三角面片表示&#xff0c;然后通过网格映射变形进行实现。关于网格这块有本经典的书可以参考&#xff0c;《ploygon mesh processing》。上面…

海外跨境电商商城源码,开启多语言多货币多商家入驻的电商新时代!

尊敬的CSDN用户们&#xff0c;你们好!我们很高兴地向您介绍一款引领未来电商潮流的全新产品——海外跨境电商商城源码!这款源码将为您打开多语言、多货币、多商家入驻的电商新时代&#xff0c;让您轻松打造出属于自己的跨境电商平台! 一、多语言支持&#xff0c;轻松拓展全球市…

创建一个小表表空间A。一个大表表空间B.并创建一个用户B1默认表空间为B。

目录 ​编辑 1、创建小表表空间 A 2、创建大表表空间 B 3、创建用户 B1 并将其默认表空间设置为 B 4、授权给用户 B1 的权限 1、创建小表表空间 A CREATE TABLESPACE A DATAFILE /u01/app/oracle/oradata/orcl/datafile_A.dbf SIZE 10M; 2、创建大表表空间 B 这个时间会略…