Java之阻塞队列和消息队列

news2024/11/20 6:14:53

目录

一.上节复习

1.什么是单列模式

2.饿汉模式

3.懒汉模式

二.阻塞队列

1.什么是阻塞队列

三.消息队列

1.什么是消息队列

2.消息队列的作用

1.解耦

 2.削峰填谷

3.异步

四.JDK中的阻塞队列

1.常见的阻塞队列

 2.向阻塞队列中放入元素---put()

3.向阻塞队列中拿出元素---take()

五.手动实现阻塞队列

1.普通队列的实现

2.堵塞队列的实现

六.实现生产者和消费者模型

1.消费速度大于生产速度

2.生产速度大于消费速度

3.虚假唤醒


一.上节复习

上节内容指路:Java之单例模式

1.什么是单列模式

单例模式是一种设计模式(设计模式:就是在特定的场景下,解决问题最优的方式,类似于棋谱),单例:顾名思义,全局只有一个实例对象

2.饿汉模式

饿汉模式:类加载的时候就完成初始化,DCL双重检查锁

public class Singleton {
    private static Singleton instance = new Singleton();
 
    private Singleton() {
 
    }
 
    public static Singleton getInstance() {
        return instance;
    }
}

 优点:书写简单,不容易出错

3.懒汉模式

懒汉模式:程序使用对象的时候才进行初始化

public class SingletonLazy {
    private static volatile SingletonLazy instance = null;
 
    private SingletonLazy() {
 
    }
 
    public static SingletonLazy getInstance() {
        if (instance == null) {
            //在获取单例对象的时候,判断是否已经被创建,没有创建则创建
            synchronized (SingletonLazy.class) {
                if (instance == null) {
                    instance = new SingletonLazy();
                }
            }
        }
 
        return instance;
    }
}

优点:避免了资源的浪费

二.阻塞队列

1.什么是阻塞队列

和之前学习过的队列一样,也是FIFO(先进先出).

入队元素时,先判断队列是否满了,如何满了就阻塞(等待),直到队列中有空余空间再入队.

出队元素时,先判断队列是否为空,如果空了就阻塞(等待),直到队列中有元素使再出队.

实例:包饺子:分为擀饺子皮和包饺子两个操作

当放饺子皮的盘子满了,擀饺子皮的人停止擀皮(等待)--入队列操作,等待有空间了再工作

当放饺子皮的盘子空了,包饺子的人就停止包饺子(等待)---出队列操作,等待有饺子皮再工作

这种模式叫做生产者消费者模型

三.消息队列

1.什么是消息队列

消息队列本质上就是阻塞队列,在此基础上为放入阻塞队列的消息打上了标签

为不同的消息进行了分组的操作

在基础数据结构上,做了一些针对应用场景的优化和实现,那么我们把这样的框架和软件,称为“中间件

消息队列就是中间件

2.消息队列的作用

1.解耦

  一个良好的程序应该是"高内聚,低耦合"的.

  高内聚:将功能强相关的代码写在一块,方便维护

  低耦合:两个相关的模块尽可以能把依赖的部分降低到最小,不要让两个系统产生强依赖

实例:当我们订单支付,订单系统会对支付系统发起请求支付的指令,调用支付系统的相关命令,这样子就是一个高耦合的例子,如果支付系统崩溃,会直接对订单系统产生影响,支付系统修改相关的代码,订单系统也要发生修改

 这个时候我们维护一个消息队列,可以对两个系统进行解耦操作,即使支付系统崩溃了,订单系统也能进行正常的工作,代码修改也不会牵一发而动全身

 2.削峰填谷

峰和谷是指消息的密集程度

例如现实中的三峡大坝 

汛期:蓄水,防止下游遭遇洪峰的冲击   削峰

旱期:把存的水进行灌溉使用    填谷

例如在双十一等流量很大的时间点,我们使用消息队列可以存储订单的信息,然后慢慢的处理订单的信息,过了时间点流量慢慢小的时候,我们依旧可以处理订单信息.没必要在大流量的时候一下子处理所有的订单,这样可能会造成服务器崩溃.

3.异步

同步:请求方必须死等对方的响应才能开始下一步操作.

异步:请求方发出请求之后,可以进行其他的操作,没必须等待对方的响应才开始操作.

比如订单系统对支付系统发出请求之后,没必须死等支付系统成功的响应就才开始其他订单的操作,而是进行其他订单的操作,同时等待这个订单的相应结果.

四.JDK中的阻塞队列

1.常见的阻塞队列

  1. LinkedBlockingQueue   基于链表
  2. ArrayBlockingQueue     基于数组
  3. PriorityBlockingQueue   基于优先级队列

在创建的时候可以指定队列的大小

 2.向阻塞队列中放入元素---put()

会抛出InterruptedException异常,阻塞队列中专用的入队的方法,不能使用offer()和add()

public class Demo01_BlockingQueue {
    public static void main(String[] args) throws InterruptedException {
        //定义一个阻塞队列
        BlockingQueue<Integer> queue=new LinkedBlockingQueue<>(3);
        //使用put()方法,不能使用add()和offer()
        queue.put(1);
        queue.put(2);
        queue.put(3);
        System.out.println("此时插入了三个元素");
        System.out.println(queue);
        queue.put(4);
        System.out.println("此时插入了四个元素");

    }
}

打印的结果:

 因为我们指定的阻塞队列的大小为3,当插入第四个元素的时候就会进入到阻塞等待的状态

3.向阻塞队列中拿出元素---take()

会抛出InterruptedException异常,阻塞队列中专用的出队的方法,不能使用poll()

public class Demo01_BlockingQueue {
    public static void main(String[] args) throws InterruptedException {
        //定义一个阻塞队列
        BlockingQueue<Integer> queue = new LinkedBlockingQueue<>(3);
        //使用put()方法,不能使用add()和offer()
        queue.put(1);
        queue.put(2);
        queue.put(3);
        System.out.println("此时插入了三个元素");
        System.out.println(queue);
//        queue.put(4);
//        System.out.println("此时插入了四个元素");
        //一定要使用take()方法,不能使用poll()方法
        System.out.println(queue.take());
        System.out.println(queue.take());
        System.out.println(queue.take());
        //进入阻塞等待状态
        System.out.println(queue.take());

    }
}

打印结果:

获取第四个元素的时候队列为空,因此进入阻塞等待的状态.

五.手动实现阻塞队列

1.普通队列的实现

在实现阻塞队列之前,我们现在实现普通队列

public class MyBlockingQueue {
    private int[] element;
    //队首下标
    private int head;
    //队尾下标
    private int tail;
    //元素个数
    private int size;

    public MyBlockingQueue() {
        this(3);
    }

    public MyBlockingQueue(int capacity) {
        element = new int[capacity];
    }

    /**
     * 入队一个元素
     *
     * @param val
     */
    public void put(int val) {
        if (size >= element.length) {
            return;
        }
        //向队尾插入元素
        element[tail] = val;
        //向后移动
        tail = (tail + 1) % element.length;
        size++;

    }

    /**
     * 出队一个元素
     *
     * @return
     */
    public int take() {
        if (size == 0) {
            return -1;
        }
        int val = element[head];
        head = (head + 1) % element.length;
        size--;
        return val;
    }

}

2.堵塞队列的实现

1.之前实现一个普通队列,底层用到了两种数据结构,一个是链表,一个是循环数组

2.阻塞队列就是在普通的队列上加入了阻塞等待(wait())唤醒操作(notify()),与synchronized相关

确定synchronized的范围,如果一个对象需要new出来使用,锁对象一般是this,其他情况具体分析

1.(线程1)当执行入队(put)操作时,判断阻塞队列满了,执行wait()操作,进入阻塞等待操作,当之后(别的线程)执行了出队列操作完成时,队列此时不满,这个时候唤醒队列.(线程1)继续完成put操作

2.(线程1)当执行出队(take)操作时,判断阻塞队列为空,执行wait()操作,进入阻塞等待操作,当之后(别的线程)执行了入队列操作完成时,队列此时不空,这个时候唤醒队列.(线程1)继续完成take操作

public class MyBlockingQueue {
    private int[] element;
    //队首下标
    private int head;
    //队尾下标
    private int tail;
    //元素个数
    private int size;

    public MyBlockingQueue() {
        this(3);
    }

    public MyBlockingQueue(int capacity) {
        element = new int[capacity];
    }

    /**
     * 入队一个元素
     *
     * @param val
     */
    public void put(int val) throws InterruptedException {
        //加入修改的范围加锁
        synchronized (this) {
            if (size >= element.length) {
                this.wait();
            }


            //向队尾插入元素
            element[tail] = val;
            //向后移动
            tail = (tail + 1) % element.length;
            size++;
            //做唤醒操作
            this.notifyAll();
        }

    }

    /**
     * 出队一个元素
     *
     * @return
     */
    public int take() throws InterruptedException {
        synchronized (this) {
            if (size == 0) {
                this.wait();
            }


            int val = element[head];
            head = (head + 1) % element.length;
            size--;
            //唤醒操作
            this.notifyAll();
            return val;
        }
    }

}

六.实现生产者和消费者模型

我们使用两个线程,一个线程模仿生产者,向阻塞队列中放元素,一个线程模仿消费者,从阻塞队列中取元素.

1.消费速度大于生产速度

public class Demo3_ProducerConsumer {
    static MyBlockingQueue queue = new MyBlockingQueue(3);

    public static void main(String[] args) {
        Thread producer = new Thread(() -> {
            int num = 1;
            while (true) {
                //生产一条消息
                try {
                    queue.put(num);
                    System.out.println("生产者生产了消息" + num);
                    num++;
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }

            }
        });
        //启动生产者
        producer.start();
        Thread consumer = new Thread(() -> {
            while (true) {
                //消费一条消息
                try {
                    int num = queue.take();
                    System.out.println("消费者消费了消息" + num);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }

            }
        });
        consumer.start();

    }
}

结果打印:

2.生产速度大于消费速度

public class Demo3_ProducerConsumer {
    static MyBlockingQueue queue = new MyBlockingQueue(10);

    public static void main(String[] args) throws InterruptedException {
        Thread producer = new Thread(() -> {
            int num = 1;
            while (true) {
                //生产一条消息
                try {
                    queue.put(num);
                    System.out.println("生产者生产了消息" + num);
                    num++;
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }

            }
        });
        //启动生产者
        producer.start();
        Thread consumer = new Thread(() -> {
            while (true) {
                //消费一条消息
                try {
                    int num = queue.take();
                    System.out.println("消费者消费了消息" + num);
                    TimeUnit.SECONDS.sleep(1);

                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }

            }
        });
        //消费者后启动,让阻塞队列满
        TimeUnit.SECONDS.sleep(2);
        consumer.start();

    }
}

打印的结果:

3.虚假唤醒

这是Java官方文档中给出虚假唤醒的定义

翻译成中文为: 线程也可以在没有被通知、中断或超时的情况下唤醒,这就是所谓的虚假唤醒。虽然这种情况在实践中很少发生,但应用程序必须通过测试本应导致线程被唤醒的条件,并在条件不满足时继续等待来防止这种情况的发生。换句话说,等待应该总是发生在循环中,就像下面这样:

所以在实践中wait条件的判断要加while循环.

阻塞队列中put和take方法的优化,并且多线程环境下共享变量要加voliatile,最终的阻塞队列的代码为:

public class MyBlockingQueue {
    private volatile int[] element;
    //队首下标
    private volatile int head;
    //队尾下标
    private volatile int tail;
    //元素个数
    private volatile int size=0;

    public MyBlockingQueue() {
        this(3);
    }

    public MyBlockingQueue(int capacity) {
        element = new int[capacity];
    }

    /**
     * 入队一个元素
     *
     * @param val
     */
    public void put(int val) throws InterruptedException {
        //加入修改的范围加锁
        synchronized (this) {
            while (size >= element.length) {
                this.wait();
            }


            //向队尾插入元素
            element[tail] = val;
            //向后移动
            tail = (tail + 1) % element.length;
            size++;
            //做唤醒操作
            this.notifyAll();
        }

    }

    /**
     * 出队一个元素
     *
     * @return
     */
    public int take() throws InterruptedException {
        synchronized (this) {
            while (size == 0) {
                this.wait();
            }


            int val = element[head];
            head = (head + 1) % element.length;
            size--;
            //唤醒操作
            this.notifyAll();
            return val;
        }
    }

}

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

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

相关文章

python url拼接的方法

Python的 url是一个常用的文件链接&#xff0c;一个文件包含多个 url&#xff0c;在很多网站中&#xff0c;我们都需要拼接多个 url。 在网上我们经常可以看到关于 Python拼接的方法介绍&#xff0c;但是很多都是非常不完整的&#xff0c;今天我们就来了解一下&#xff0c;比较…

晨控CK-FR208-EIP与欧姆龙PLC工业Ethernet/IP协议通讯指南

CK-FR208-EIP 是一款支持标准工业 Ethernet/IP 协议的多通道工业 RFID 读写器&#xff0c;读卡器 工作频率为 13.56MHZ&#xff0c;支持对 I-CODE 2、I-CODE SLI 等符合 ISO15693 国际标准协议格式标签的读写。 读卡器同时支持标准工业通讯协议 Ethernet/IP&#xff0c;方便用…

Linux使用rsync同步文件

1.rsync的概念 rsync&#xff0c;remote synchronize顾名思义就知道它是一款实现远程同步功能的软件&#xff0c;它在同步文件的同时&#xff0c;可以保持原来文件的权限、时间、软硬链接等附加信息。 2.查看rsync 查看服务器端rsync版本 rsync --version rsync命令选项 -…

从GFS到GPT,AI Infra的激荡20年

导读 最近AIGC和LLM的浪潮层层迭起&#xff0c;大有把AI行业过去十年画的饼&#xff0c;一夜之间完全变现的势头。而AI Infra&#xff08;构建AI所需的基础设施&#xff09;&#xff0c;也成了讨论的焦点之一。大众对AI Infra的关注点&#xff0c;往往放在AI算力上——比如A100…

创作纪念日|我在CSDN的第365天(内含粉丝福利)

创作纪念日 大家好&#xff0c;我是陈橘又青&#xff0c;最近因为一直在备考&#xff0c;所以没怎么更新博客&#xff0c;今天起来和往常一样看了一眼私信&#xff0c;发现了一条来自CSDN官方的私信。 打开一看&#xff0c;原来是创作一周年的通知&#xff0c;回想起来&#…

Python数据分析:NumPy、Pandas和Matplotlib的使用和实践

在现代数据分析领域中&#xff0c;Python已成为最受欢迎的编程语言之一。Python通过庞大的社区和出色的库支持&#xff0c;成为了数据科学家和分析师的首选语言。在Python的库中&#xff0c;NumPy、Pandas和Matplotlib是三个最为重要的库&#xff0c;它们分别用于处理数值数组、…

基于密度的无线传感器网络聚类算法的博弈分析(Matlab代码实现)

目录 &#x1f4a5;1 概述 &#x1f4da;2 运行结果 &#x1f389;3 参考文献 &#x1f468;‍&#x1f4bb;4 Matlab代码 &#x1f4a5;1 概述 提高能源效率是无线传感器网络面临的关键挑战之一&#xff0c;无线传感器网络日益普遍。由于节点&#xff08;传感器&#xff…

服务高可用保障:服务限流,Nginx实现服务限流

一、前言 1.1什么是限流&#xff1f; 限流存在于高可用服务中。 用于高可用的保护手段&#xff0c;主要包括&#xff1a;缓存&#xff0c;降级&#xff0c;限流 限流&#xff1a;只允许指定的事件进入系统&#xff0c;超过的部分将被拒绝服务&#xff0c;排队或者降级处理。 …

【零基础学web前端】html学习,表格标签,列表标签,表单标签(form和input),无语义标签div与span

前言: 大家好,我是良辰丫,今天我们就开始进入前端知识的学习&#x1f49e;&#x1f49e; &#x1f9d1;个人主页&#xff1a;良辰针不戳 &#x1f4d6;所属专栏&#xff1a;零基础学web前端 &#x1f34e;励志语句&#xff1a;生活也许会让我们遍体鳞伤&#xff0c;但最终这些伤…

组织学图像弱监督腺体分割的在线简易示例挖掘

文章目录 Online Easy Example Mining for Weakly-Supervised Gland Segmentation from Histology Images摘要本文方法分割 实验结果 Online Easy Example Mining for Weakly-Supervised Gland Segmentation from Histology Images 摘要 背景 开发AI辅助的组织学图像腺体分割方…

DNDC模型在土地利用变化、未来气候变化下的建模方法及温室气体时空动态模拟

由于全球变暖、大气中温室气体浓度逐年增加等问题的出现&#xff0c;“双碳”行动特别是碳中和已经在世界范围形成广泛影响。“十四五”时期&#xff0c;我国生态文明建设进入了以降碳为重点战略方向、推动减污降碳协同增效、促进经济社会发展全面绿色转型、实现生态环境质量改…

除氟树脂,除氟树脂用啥再生,离子交换除氟,矿井水除氟

氟化物选择吸附树脂 Tulsimer CH-87 是一款去除水溶液中氟离子的专用的凝胶型选择性离子交换树脂。它是具有氟化物选择性官能团的交联聚苯乙烯共聚物架构的树脂。 去除氟离子的能力可以达到 1ppm 以下的水平。中性至碱性的PH范围内有较好的工作效率&#xff0c;并且很容易再生…

2023年苹果企业开发者证书申请流程

第一步&#xff1a;注册apple ID&#xff0c;注意&#xff0c;要使用公司官网域名相关的企业邮箱账号注册&#xff0c;前提是公司要有企业邮箱&#xff0c;开通企业邮箱可用163代理的&#xff0c;也可以自己搭建。 第二步&#xff1a;在移动设备上登录该apple ID&#xff0c;并…

.Net中间件的概念---杨中科笔记

什么是中间件&#xff1f; 中间件是ASP.NET Core的核心组件&#xff0c;MVC框架、响应缓存、身份验证、CORS、Swagger等都是内置中间件。 中间件组成一个管道&#xff0c;整个ASP.NET Core的执行过程就是HTTP请求和响应按照中间件组装的顺序在中间件之间流转的过程。开发人员可…

一种KV存储的GC优化实践

作者&#xff1a;vivo 互联网服务器团队- Yuan Jian Wei 从内部需求出发&#xff0c;我们基于TiKV设计了一款兼容Redis的KV存储。基于TiKV的数据存储机制&#xff0c;对于窗口数据的处理以及过期数据的GC问题却成为一个难题。本文希望基于从KV存储的设计开始讲解&#xff0c;到…

MySQL 高级(进阶) SQL 语句三 存储过程

1.1 什么是存储过程 存储过程是一组为了完成特定功能的SQL语句集合。 存储过程在使用过程中是将常用或者复杂的工作预先使用SQL语句写好并用一个指定的名称存储起来&#xff0c;这个过程经编译和优化后存储在数据库服务器中。当需要使用该存储过程时&#xff0c;只需要调用它…

中国物种物候和地面物候数据获取方法

物候学是研究自然界的植物&#xff08;包括农作物&#xff09;、动物和环境条件&#xff08;气候、水文、土壤条件&#xff09;的周期变化之间相互关系的科学。它的目的是认识自然季节现象变化的规律&#xff0c;以服务于农业生产和科学研究。 [3-4] 物候既可指生物的周期性…

从0到1复现斯坦福羊驼(Stanford Alpaca 7B)

近日&#xff0c;Meta开源了他们的LLaMA系列模型&#xff0c;包含了参数量为7B/13B/33B/65B的不同模型&#xff0c;然而&#xff0c;原模型的效果较差&#xff08;如生成的结果文不对题、以及无法自然地结束生成等&#xff09;。因此&#xff0c;斯坦福的 Alpaca 模型基于 LLaM…

基于AT89C51单片机的简易电梯上下楼层间移动系统

点击链接获取Keil源码与Project Backups仿真图&#xff1a; https://download.csdn.net/download/qq_64505944/87776511?spm1001.2014.3001.5503 源码获取 主要内容&#xff1a; 采用单片AT89C51芯片进行电梯控制系统的设计方法&#xff0c;主要阐述如何使用单机进行编程来实…

《斯坦福数据挖掘教程·第三版》读书笔记(英文版)Chapter 7 Clustering

来源&#xff1a;《斯坦福数据挖掘教程第三版》对应的公开英文书和PPT Chapter 7 Clustering The requirements for a function on pairs of points to be a distance measure are that: Distances are always nonnegative, and only the distance between a point and itse…