阻塞队列与生产者消费者模型


前言

前面我们介绍了,notify和wait方法的说明,这里我们会引入一个综合的应用,对这个notify方法和wait方法的使用,让大家进一步了解阻塞队列,顺便了解完阻塞队列以后,我们会学习阻塞队列的一个应用消费者和生产者模式.大家尽请期待.

一什么是阻塞队列

实际上阻塞队列,就是起到一个阻塞等待的觉结果,其实它就是一个特殊的队列,它遵循一个先进先出的原则。
我来列举一下阻塞队列的俩个特性:
1.当队列满的时候,继续入队列就会阻塞等待,直到有其他线程从队列中取走元素。
2.当队列为空的时候,继续入队也会阻塞等待,等待到队列不为空为止。
在进行上述的一定特点介绍以后,我觉得吧,我们还是用具体的一个生活场景来模拟一下阻塞队列的一个特征。
例如:
请添加图片描述

一个典型的阻塞队列场景是在餐厅点餐,当客人在餐厅点餐时,厨师会将菜品的订单放入一个阻塞队列中,等待服务员逐一取出,并传递给厨房,直到所有订单都被制作完成并上菜
为什么说阻塞队列会有这样的效果呢?,下面我们一一解释这个东西.看下面的解释。
如果队列已满,新的订单将被阻塞,直到队列中有空间。类似地,如果队列为空,则服务员会等待,直到有新的订单加入队列,以便能够取出并传递给厨房。在这种情况下,阻塞队列能够帮助管理餐厅的订单处理,确保良好的订单流程和服务质量。
介绍完阻塞队列的具体场景以后,我们下面会介绍阻塞队列的一个对象类,还有阻塞队列的实际开发场景,大家看我继续往下说。


二.Java提供的阻塞队列的对象

在 Java 标准库中内置了阻塞队列. 如果我们需要在一些程序中使用阻塞队列, 直接使用标准库中的即可.
BlockingQueue 是一个接口. 真正实现的类是 LinkedBlockingQueue.
put 方法用于阻塞式的入队列, take 用于阻塞式的出队列.
BlockingQueue 也有 offer, poll, peek 等方法, 但是这些方法不带有阻塞特性.
接下来我们来看一个简单的java代码实例,来看看,java内库的阻塞队列的使用.

public class ThreadDemo23 {
    /*
    java内部阻塞队列
     */
    public static void main(String[] args) throws InterruptedException {
        BlockingQueue<String>queue=new LinkedBlockingQueue<>();
        //1.入队列
        queue.put("hello01");
        queue.put("hello02");
        queue.put("hello03");
        queue.put("hello04");
        queue.put("hello05");
        queue.put("hello06");
        //2.出队列
        String result = null;
        result = queue.take();
        System.out.println(result);
        result = queue.take();
        System.out.println(result);
        result = queue.take();
        System.out.println(result);
        result = queue.take();
        System.out.println(result);
        result = queue.take();
        System.out.println(result);
        result = queue.take();
        System.out.println(result);
        result = queue.take();
        System.out.println(result);


    }
}

在这里插入图片描述
可以看出在我们上述的程序中,我们如入队6次之后,我们出队了6次,这时候队列为空,显然是已经陷入了阻塞等待.看程序运行结果,你们也看的出来,程序的运行并没有结束.


三.实际的应用场景 -生产者消费者模式

什么是生产者和消费者模型呢?大家想的还是不要太过于复杂,这就是一种常见的代码写法,你不要跟Java的设计模式相关联。我们还是来看一看概念,是什么吧!生产者和消费者模式就是彼此之间不直接通信,而是通过阻塞队列进行通讯,所以生产者生产完数据以后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是朱姐从阻塞队列里面取,大概就是这么一个情况。
当然我们还是会用一个实际的例子去说明,大家都包过饺子吧。
我们就把包饺子这个例子,分解开来看。
1.擀饺子皮
2.包饺子
当然我们有很多种假设,我们既然把它列出了俩个步骤,给这俩个步骤划分生产者和消费者。进行一个场景模拟,如下图所示:
请添加图片描述
这样我们就说明了生产者,消费者这样的角色是针对某个资源来说的,这里的资源就是饺子皮。
那阻塞队列自然而然就是那个案板。
案板就是一个交易场所。
接下来我们来展示一下具体的java代码去模拟这个包饺子的一个场景,就是所谓的生产者和消费者模式.

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

public class ThreadDemo24 {
    public static void main(String[] args) {
        //申请一个阻塞队列
        BlockingQueue<Integer> blockingQueue = new LinkedBlockingQueue<>();
        //开始模拟生产者消费者模式
        //消费者
        Thread t1=new Thread(() ->{
            while(true){

                try {
                    int value = blockingQueue.take();
                    System.out.println("消费元素"+ value);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t1.start();
        //生产者
        Thread t2=new Thread(() ->{
            int value=0;
            while (true){


                try {
                    System.out.println("生产元素" +value);
                    blockingQueue.put(value);
                    value++;
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        });
        t2.start();


    }
}

上述代码, 让生产者, 每隔 1s 生产一个元素.
让消费者则直接消费, 不受限制.


四.阻塞队列和生产者消费者模式的具体模拟实现

阻塞队列和生产者消费者模式

首先我们实现一个阻塞队列,我们可以进行以下三个步骤:
1.实现一个普通队列
2.加上线程安全
3.加上阻塞功能
实现完以上三个步骤,我们的阻塞队列就算是已经实现了阻塞队列,接下来让我们一一的取模拟实现,具体的步骤如下:
实现一个普通队列

class MyBlockingQueue{
private int[] items=new int[10000];
//约定队列的有效元素
    private int head=0;//首部
    private int tail=0;//尾部
    private int size=0;//统计个数
    //入队列
    public void put(int elem) throws InterruptedException {
        while (size ==items.length){
            //队列满了,插入失败
         	return;
        }
        //把新元素放到tail
        items[tail]=elem;
        tail++;
        //tail到达尾部,就让tail从头再来,起到一个循环利用的一个方式
        if (tail ==items.length){
            tail=0;
        }
        size++;
        this.notify();
    }
    //出队列
    public Integer take() throws InterruptedException {
        while (size ==0){
           return;
        }
        int value=items[head];
        head++;
        if (head ==items.length){
            head=0;
        }
        size--;     
        return value;
    }

}

加上线程安全
这里我们加上线程安全的措施

class MyBlockingQueue{
private int[] items=new int[10000];
//约定队列的有效元素
   	volatile  private int head=0;//首部
    volatile private int tail=0;//尾部
    volatile private int size=0;//统计个数
    //入队列
    synchronized public void put(int elem) throws InterruptedException {
        while (size ==items.length){
            //队列满了,插入失败
         	return;
        }
        //把新元素放到tail
        items[tail]=elem;
        tail++;
        //tail到达尾部,就让tail从头再来,起到一个循环利用的一个方式
        if (tail ==items.length){
            tail=0;
        }
        size++;
    }
    //出队列
  	synchronized  public Integer take() throws InterruptedException {
        while (size ==0){
           return;
        }
        int value=items[head];
        head++;
        if (head ==items.length){
            head=0;
        }
        size--;     
        return value;
    }

}

加上阻塞功能

class MyBlockingQueue {
    private int[] items = new int[1000];
    // 约定 [head, tail) 队列 的有效元素
    volatile private int head = 0;
    volatile private int tail = 0;
    volatile private int size = 0;

    // 入队列
    synchronized public void put(int elem) throws InterruptedException {
        while (size == items.length) {
            // 队列满了, 插入失败.
            // return;
            this.wait();
        }
        // 把新元素放到 tail 所在位置上
        items[tail] = elem;
        tail++;
        // 万一 tail 达到末尾, 就需要让 tail 从头再来.
        if (tail == items.length) {
            tail = 0;
        }
        // tail = tail % items.length;
        size++;
        this.notify();
    }

    // 出队列
    synchronized public Integer take() throws InterruptedException {
        while (size == 0) {
            // return null;
            this.wait();
        }
        int value = items[head];
        head++;
        if (head == items.length) {
            head = 0;
        }
        size--;
        this.notify();
        return value;
    }
}

看以下图表进行说明:
在这里插入图片描述
我接下来将展示完整的程序:

//先实现一个队列
class MyBlockingQueue {
    private int[] items = new int[1000];
    // 约定 [head, tail) 队列 的有效元素
    volatile private int head = 0;
    volatile private int tail = 0;
    volatile private int size = 0;

    // 入队列
    synchronized public void put(int elem) throws InterruptedException {
        while (size == items.length) {
            // 队列满了, 插入失败.
            // return;
            this.wait();
        }
        // 把新元素放到 tail 所在位置上
        items[tail] = elem;
        tail++;
        // 万一 tail 达到末尾, 就需要让 tail 从头再来.
        if (tail == items.length) {
            tail = 0;
        }
        // tail = tail % items.length;
        size++;
        this.notify();
    }

    // 出队列
    synchronized public Integer take() throws InterruptedException {
        while (size == 0) {
            // return null;
            this.wait();
        }
        int value = items[head];
        head++;
        if (head == items.length) {
            head = 0;
        }
        size--;
        this.notify();
        return value;
    }
}


public class ThreadDemo25 {
    public static void main(String[] args) {
        MyBlockingQueue queue = new MyBlockingQueue();
        // 消费者
        Thread t1 = new Thread(() -> {
            while (true) {
                try {
                    int value = queue.take();
                    System.out.println("消费: " + value);
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        // 生产者
        Thread t2 = new Thread(() -> {
            int value = 0;
            while (true) {
                try {
                    System.out.println("生产: " + value);
                    queue.put(value);
                    value++;
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        t1.start();
        t2.start();



        System.out.println("hello");

    }
}

我们可以看出生产到1000个产品,生产到一定的条件以后,我们就开始生产一个消费一个.自然大家都会就是一个生产者消费者模式.
在这里插入图片描述


五.生产者消费者模式究竟能解决什么问题

换句话说,我们阻塞队列能够解决什么问题.最终在里面充当了一个什么角色.

5.1解耦合

我先来展示一下耦合度高的一个例子
在这里插入图片描述
我们来用生产者消费者模式去优化一下:
在这里插入图片描述
现在来粗略的总结一下生产者消费者模式的好处如下:
1.提高了系统的可维护性。由于生产者和消费者之间的耦合度降低,因此当某一个模块需要修改时,不需要对整个系统进行修改,只需要修改特定的模块即可。

2.提高了系统的灵活性。生产者和消费者之间的解耦合使得系统能够更好地适应变化。例如,当消费者无法立即处理生产者产生的数据时,可以通过调整阻塞队列的大小来缓解生产者和消费者之间的压力。

3.提高了系统的可扩展性。由于生产者和消费者之间的解耦合,因此可以方便地扩展系统中的生产者和消费者,而不需要修改整个系统的代码。


5.2 削峰填谷

看一下没有进行削峰填谷,会出现什么情况,如下图所示:
在这里插入图片描述


然后我们利用消费者生产者模式进行优化之后,会出现一个什么样的情况呢?
在这里插入图片描述


最后我们来总结一下:
阻塞队列能够起到削峰填谷的作用是因为它能够平衡生产者和消费者的速度差异,让它们之间的工作能够更加协调。当生产者的速度过快时,数据会被暂存在阻塞队列中,不会立即传递给消费者,这样就能够缓解生产者对消费者的冲击,达到削峰的效果。当消费者的速度过快时,它会从阻塞队列中获取数据,如果队列为空,则消费者线程会被阻塞,直到有数据可供消费。这样就能够填谷,避免了消费者速度过快造成的浪费。

因此,通过阻塞队列可以让生产者和消费者之间实现松耦合,生产者可以专注于生成数据,而不必考虑消费者的速度和状态;消费者也可以专注于处理数据,而不必担心生产者的速度和状态。这种解耦合的方式能够有效地提高系统的稳定性和可靠性。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

忘忧记

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值