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

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就是业界常见使用的消息队列

 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

(论文阅读24/100)Visual Tracking with Fully Convolutional Networks

文献阅读笔记&#xff08;sel - CNN&#xff09; 简介 题目 Visual Tracking with Fully Convolutional Networks 作者 Lijun Wang, Wanli Ouyang, Xiaogang Wang, and Huchuan Lu 原文链接 http://202.118.75.4/lu/Paper/ICCV2015/iccv15_lijun.pdf 【DeepLearning】…

easyexcel==省市区三级联动

省市区三级联动&#xff0c;不选前面的就没法选后面的 package com.example.demoeasyexcel.jilian2; import com.alibaba.excel.write.metadata.holder.WriteSheetHolder; import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder; import org.apache.poi.ss.use…

IT 基础设施监控工具

IT 基础架构监控作为一个整体&#xff0c;是关于跟踪网络环境中所有 IT 资产的运行状况和性能&#xff0c;网络管理系统收集有关各种指标的数据&#xff0c;例如可用性、运行状况、性能和利用率&#xff0c;然后&#xff0c;IT 基础架构监控将这些数据转换为有用的统计数据&…

【Python】数据分析案例:世界杯数据可视化 | 文末送书

文章目录 前期数据准备导入数据 分析&#xff1a;世界杯中各队赢得的比赛数分析&#xff1a;先打或后打的比赛获胜次数分析&#xff1a;世界杯中的抛硬币决策分析&#xff1a;2022年T20世界杯的最高得分者分析&#xff1a;世界杯比赛最佳球员奖分析&#xff1a;最适合先击球或追…

Python+reuqests自动化接口测试

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

【QT】qt打包程序后无法正常启动

本人在自己电脑上打包Qt程序后可以正常运行&#xff0c;但换了个电脑就无法运行了&#xff0c;显示应用程序无法正常启动&#xff08;0xc000007b&#xff09;。 造成这种情况的原因是因为系统变量的原因&#xff0c;我用的win10自带的cmd。 应该采用Qt自带的cmd&#xff0c;打开…

五种常见的IO模型

目录 一. IO的概述 1.1 什么是IO 1.2 IO的效率问题 1.3 同步IO和异步IO的概念 二. 阻塞式IO 三. 非阻塞式IO 四. 信号驱动式IO 五. IO多路复用 六. 异步IO 七. 总结 一. IO的概述 1.1 什么是IO IO&#xff0c;表示输入输出&#xff0c;即&#xff1a;InPut / OutPut…

Day22力扣打卡

打卡记录 替换子串得到平衡字符串&#xff08;滑动窗口&#xff09; 链接 由于是以后统计替换的子串&#xff0c;不可以直接使用hash表统计的每个次数大于 n / 4 的字符&#xff0c;再将其次数减去平衡数来得到答案&#xff0c;根据字符串的连贯性&#xff0c;使用 滑动窗口 …

Linux服务器配置信息查询命令

Linux服务器配置信息查询命令 一、查看CPU信息 查询系统的CPU的详细信息&#xff0c;包括每个处理器的型号、频率、缓存等级以及每个核心的数量。cat /proc/cpuinfo二、查看内存信息 查询系统的内存信息&#xff0c;包括可用内存、已用内存和缓存等。cat /proc/meminfo三、查…

飞控硬件介绍及其主要传感器特性解析

飞行控制器是无人机的关键组件之一&#xff0c;它主要由主控单片机、IMU传感器、电源和输出IO等部分构成。这些硬件和传感器的特性对于无人机的二次开发至关重要&#xff0c;其性能和质量直接关系到无人机的稳定性、飞行性能和功能扩展能力。 本文将带领新手开发者深入了解飞行…

【文件IO】认识文件

文章目录 认识文件文件的结构和目录文件路径 认识文件 我们先来认识狭义上的文件(file)&#xff0c;针对硬盘这种持久化存储的I/O设备&#xff0c;当我们想要进行数据保存时&#xff0c;往往不是保存一个整体&#xff0c;而是独立成一个个单位进行保存&#xff0c;这个独立的单…

jbase代码生成器(成型篇)

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

创建一个事务级临时表或者会话级临时表继续测试,在什么情况下临时表里的数据会消失

目录 一、测试事务级临时表 1、创建事务级临时表 2、插入测试数据 3、查看表中的数据 4、提交事务 5、再次查看表中数据 二、测试会话级临时表 1、创建会话级临时表 2、插入测试数据 3、查看表中的数据 4、提交事务再次查看数据 5、关闭当前会话 6、再次进入数据库…

项目管理之如何出道(下)

前言 是谁用烛火照亮整个中国&#xff1f;是一伙伙行走在高压线上的电力工人&#xff1b; 是谁用水枪保护千家万户&#xff1f;是一组组穿梭于大街小巷的消防队伍&#xff1b; 是谁用身体捍卫国防边境&#xff1f;是一队队跋涉在高山深林的可爱战士。 那么作为IT业界的我们&…

GPIO实验:ARM汇编代码实现LED灯亮灭控制

GPIO实验&#xff1a;ARM汇编代码实现LED灯亮灭控制 一、 汇编工程模板Makefile分析 NAMEasm-led #指定编译的源文件名字 CROSS_COMPILE arm-linux-gnueabihf- #指定交叉编译工具链前缀CC $(CROSS_COMPILE)gcc #指定gcc名字LD $(CROSS_COMPILE)ld #指定链接器名字…

“第六十五天”

固态硬盘&#xff1a;SSD 原理&#xff1a;基于闪存技术Flash Memory &#xff0c;属于电可擦除ROM&#xff0c;即EEPROM&#xff1b; 由闪存翻译层和存储介质组成&#xff1b;闪存翻译层负责翻译逻辑块号&#xff0c;找到对应页&#xff0c;存储介质是由多个闪存芯片构成的&…

Pycharm常用快捷键和替换正则表达式

原生快捷键的使用&#xff1a; 1.CtrlF&#xff1a;查找 2.CtrlZ&#xff1a;返回上一步 3.Alt 鼠标左键选择&#xff1a;多行同时编辑&#xff08;上、下、左、右键能够移动光标&#xff09; 按住Ctrl,左键点击&#xff0c;定位光标 编辑过程 URL常用的替换正则表达式&am…

阿里云99元的主机到底怎么样?

我是卢松松&#xff0c;点点上面的头像&#xff0c;欢迎关注我哦&#xff01; 在云栖大会上&#xff0c;阿里云推出了一款绝对超级超值的99元云服务器&#xff0c;并号称是11月销量王。什么?云栖大会11月2号结束的&#xff0c;你就号称11月销量王&#xff0c;这是未卜先知啊。…

【算法 | 数论 No.1】AcWing1246. 等差数列

个人主页&#xff1a;兜里有颗棉花糖 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 兜里有颗棉花糖 原创 收录于专栏【手撕算法系列专栏】【AcWing算法提高学习专栏】 &#x1f354;本专栏旨在提高自己算法能力的同时&#xff0c;记录一下自己的学习过程&a…

【异常----finally和自定义异常】

文章目录 finally练习问题 异常的处理流程【异常处理流程总结】自定义异常类 finally 有些特定的代码&#xff0c;不论程序是否发生异常&#xff0c;都需要执行&#xff0c;比如程序中打开的资源&#xff1a;在程序正常或者异常退出时&#xff0c;必须要对资源进进行回收。另外…