【JavaEE】——阻塞队列,生产消费者模型(较难)

简介: 阻塞队列,生产消费者模型,分布式,自己实现一个阻塞队列,阻塞队列带来的多线程安全问题(wait唤醒问题)

 阿华代码,不是逆风,就是我疯,你们的点赞收藏是我前进最大的动力!!希望本文内容能够帮助到你!

目录

一:阻塞队列

1:概念

2:阻塞队列与普通队列比较

二:“生产者消费者模型”——包饺子

1:包饺子流程

2:分工协作

(1)解释

(2)问题

三:“生产者消费者模型”——分布式系统

1:分布式模型

2:队列阻塞优化

3:缺点

4:优点

①解耦合

②缓冲压力(削峰填谷)

四:自己实现一个阻塞队列

前引:库中自带阻塞队列的数据结构

编辑

1:自己实现的一个队列

2:线程安全问题

(1)问题一

(2)阻塞队列部分怎么写

①用wait

②唤醒问题

③解决方法:


一:阻塞队列

1:概念

对于一个满的队列,入队操作就会陷入阻塞,直到这个队列有元素出队后,才可以往队列里面加入元素。

对于一个空的队列,出队操作就会陷入阻塞,直到这个队列有元素入队后,才可以对队列进行出队操作

2:阻塞队列与普通队列比较

阻塞队列在多线程中是比价安全的

二:“生产者消费者模型”——包饺子

1:包饺子流程

①和面(一般一个人即可,不适用于多线程)

②撵饺子皮

③包饺子

上述②③可以多线程进行

假设现在有三个滑稽老铁包饺子

image.gif 编辑

假设,每个滑稽老铁拿到擀面杖,擀了一个皮,包了一个饺子,在这个过程中,滑稽老铁会争夺擀面杖(锁竞争),虽然比单线程快,但是效率还是很低

2:分工协作

image.gif 编辑

(1)解释

上述图,一号老铁负责专门撵饺子皮(生产),2,3号负责包饺子,桌子(阻塞队列)负责传递饺子皮,大大提高了包饺子的效率。

(2)问题

①1号滑稽撵饺子皮的速度远远大于包饺子的速度,导致桌子上全是饺子皮,此时桌子就相当于队列阻塞

②1号滑稽撵饺子皮的速度远远小于包饺子的速度,导致桌子上是空的,2号3号空闲,此时桌子就相当于队列空

三:“生产者消费者模型”——分布式系统

1:分布式模型

通过上面的简述我们来看实际开发中是怎样的模型

image.gif 编辑

实际开发过程中,服务器的整个功能往往是由多个服务器分工,它们彼此间通过网络通信进行交互。

2:队列阻塞优化

但在上述图中,A与B,A与C之间的耦合性比较强,有一个挂了,很可能就会影响到其它的服务器,于是我们引入队列阻塞

image.gif 编辑

A和BCD之间不是直接交互了,而是通过队列这个中间桥梁进行交互,如果B挂了,也不会影响到ACD

3:缺点

①系统更复杂了,要维护的服务器变多

②效率降低,有阻塞队列这个中间商,必然会增加开销了

4:优点

①解耦合

②缓冲压力(削峰填谷)

image.gif 编辑

image.gif 编辑

四:自己实现一个阻塞队列

前引:库中自带阻塞队列的数据结构

在Java标准库中提供了三种现成的带有阻塞队列的数据结构

image.gif 编辑

其中它们的入队列有两种方法put(自带阻塞效果)offer(不带有阻塞效果)

下面我们举例一种

ArrayBlockingQueue queue2 = new ArrayBlockingQueue<>(100);
        try {
            queue2.put("put方法不带阻塞功能");
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        queue2.offer("offer方法带有阻塞功能");
        try {
            System.out.println(queue2.take());
            System.out.println(queue2.take());
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

image.gif

image.gif 编辑

1:自己实现的一个队列

package thread;
/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: Hua YY
 * Date: 2024-09-25
 * Time: 17:47
 */
class MyQueue{
    String[] elems = null;
    int head = 0;//记录出元素时
    int tail = 0;//记录进元素时
    int size = 0;//当前队列中有多少个元素
    public MyQueue(int capacity){
        elems = new String[capacity];
    }
    public void put(String elem){
        if (size >= elems.length ){
            //此时队列满了放不了要在这写阻塞
            return;
        }
        elems[tail] = elem;
        tail++;
        if(tail >= elems.length){
            tail = 0;
        }
        size++;
    }
    public String take(){
        if(size <= 0){
            return null;
        }
        String elem = elems[head];
        head++;
        if (head >= elems.length){
            head = 0;
        }
        size--;
        return elem;
    }
}
public class ThreadDemon31 {
    public static void main(String[] args) {
        MyQueue queue = new MyQueue(100);
        queue.put("aaa");
        queue.put("bbb");
        queue.put("ccc");
        queue.put("ddd");
        System.out.println("开始出队列");
        System.out.println(queue.take());
        System.out.println(queue.take());
        System.out.println(queue.take());
        System.out.println(queue.take());
    }
}

image.gif

image.gif 编辑

2:线程安全问题

image.gif 编辑

(1)问题一

①打包成这样可以吗——不行,size最后会被写两遍

②解决方式:给整个put方法内部都加上synchronized

image.gif 编辑

(2)阻塞队列部分怎么写

①用wait

image.gif 编辑

②唤醒问题

image.gif 编辑

可以看到上述图例:假设现在队列满,两个put都阻塞,take出了 一个元素,唤醒了第一个put,第一个put又唤醒了第二个put,这就出问题了

③解决方法:

详细看明白上面举的例子后继续~~

我们知道对于,wait(等待)和notify(唤醒)中间隔着的几秒,对于计算机来说,可能就会发生翻天覆地的变化。这个if条件句就是,被唤醒后,其实现状不一定像一开始满足这个if条件句了

image.gif 编辑

于是我们引入替换成——while语句,句内的wait被唤醒后会再一次进行条件判断,如果此时条件,不满足,会再一次的陷入wait等待。

在java编译器中,也是推荐wait和while循环配套使用。 image.gif 编辑

相关文章
|
1月前
|
消息中间件 设计模式 安全
《C++中高效线程安全的生产者 - 消费者模型设计秘籍》
生产者-消费者模型是现代C++多线程编程中的经典设计模式,广泛应用于网络服务器、消息队列等场景。该模型通过生产者生成数据、消费者处理数据的方式,解决多线程间的数据交互问题。设计高效且线程安全的生产者-消费者模型,需考虑线程安全、选择合适的共享数据结构、使用互斥锁和条件变量、优化性能及处理异常情况,以确保程序的稳定性和性能。
|
2月前
|
消息中间件 NoSQL 关系型数据库
【多线程-从零开始-捌】阻塞队列,消费者生产者模型
【多线程-从零开始-捌】阻塞队列,消费者生产者模型
33 0
|
4月前
|
消息中间件 Java Kafka
Kafka不重复消费的终极秘籍!解锁幂等性、偏移量、去重神器,让你的数据流稳如老狗,告别数据混乱时代!
【8月更文挑战第24天】Apache Kafka作为一款领先的分布式流处理平台,凭借其卓越的高吞吐量与低延迟特性,在大数据处理领域中占据重要地位。然而,在利用Kafka进行数据处理时,如何有效避免重复消费成为众多开发者关注的焦点。本文深入探讨了Kafka中可能出现重复消费的原因,并提出了四种实用的解决方案:利用消息偏移量手动控制消费进度;启用幂等性生产者确保消息不被重复发送;在消费者端实施去重机制;以及借助Kafka的事务支持实现精确的一次性处理。通过这些方法,开发者可根据不同的应用场景灵活选择最适合的策略,从而保障数据处理的准确性和一致性。
354 9
|
4月前
|
安全 Java
Java模拟生产者-消费者问题。生产者不断的往仓库中存放产品,消费者从仓库中消费产品。其中生产者和消费者都可以有若干个。在这里,生产者是一个线程,消费者是一个线程。仓库容量有限,只有库满时生产者不能存
该博客文章通过Java代码示例演示了生产者-消费者问题,其中生产者在仓库未满时生产产品,消费者在仓库有产品时消费产品,通过同步机制确保多线程环境下的线程安全和有效通信。
|
7月前
|
消息中间件 存储 安全
【深入浅出RocketMQ原理及实战】「底层原理挖掘系列」透彻剖析贯穿RocketMQ的消息顺序消费和并发消费机制体系的原理分析
【深入浅出RocketMQ原理及实战】「底层原理挖掘系列」透彻剖析贯穿RocketMQ的消息顺序消费和并发消费机制体系的原理分析
105 0
|
7月前
|
消息中间件 Java 调度
【深度挖掘RocketMQ底层源码】「底层源码挖掘系列」透彻剖析贯穿RocketMQ的消费者端的运行调度的流程(Pull模式)
【深度挖掘RocketMQ底层源码】「底层源码挖掘系列」透彻剖析贯穿RocketMQ的消费者端的运行调度的流程(Pull模式)
69 1
|
7月前
|
消息中间件 Java RocketMQ
【深度挖掘 RocketMQ底层源码】「底层源码挖掘系列」抽丝剥茧贯穿RocketMQ的消费者端的运行核心的流程(Pull模式-下)
【深度挖掘 RocketMQ底层源码】「底层源码挖掘系列」抽丝剥茧贯穿RocketMQ的消费者端的运行核心的流程(Pull模式-下)
51 1
|
7月前
|
消息中间件 存储 NoSQL
【深度挖掘 RocketMQ底层源码】「底层源码挖掘系列」透彻剖析贯穿RocketMQ的消费者端的运行核心的流程(Pull模式-上)
【深度挖掘 RocketMQ底层源码】「底层源码挖掘系列」透彻剖析贯穿RocketMQ的消费者端的运行核心的流程(Pull模式-上)
62 1
|
7月前
|
Java
生产与消费(多线程练习题)
生产与消费(多线程练习题)
41 0
【软考学习9】进程的同步与互斥、生产消费者模型
【软考学习9】进程的同步与互斥、生产消费者模型
202 0

热门文章

最新文章