阻塞队列详细介绍

10 篇文章 0 订阅
1 篇文章 0 订阅

目录

阻塞队列

"生产者消费者模型"

生产者消费者模型的好处

实现了发送方和接受方之间的"解耦"

"削峰填谷",保证系统的稳定性

模型代码

 使用标准库提供的阻塞队列

实现一个阻塞队列


阻塞队列

阻塞队列,是一个特殊的队列,虽然继承了队列先进先出的特性,但是也带有特殊的功能:

1)如果队列为空,执行出队列操作,就会阻塞,一直阻塞到另一个线程往队列里添加元素(队列不为空)为止.

2)如果队列满了,执行入队列操作,也会阻塞,一直阻塞到另一个线程取走队列中的元素(队列不满)为止.

基于阻塞队列的阻塞特性,可以实现"生产者消费者模型"

"生产者消费者模型"

那么什么是"生产者消费者模型"呢??

举一个例子来理解:A和B和C包饺子(擀饺子皮+包饺子),有两种包法

1.A和B和C各自进行擀饺子皮+包饺子 操作

2.A负责擀饺子皮,B和C负责包饺子

上述两种方式,显然是第二种效率更高一些,第一种方式大家都会竞争擀面杖从而影响效率.

第二种方式,我们就可以理解为"生产者消费者模型",A负责擀饺子皮,那么A就是生产者,B和C拿走A的饺子皮来包饺子,B和C就是消费者.

那么对应到多线程中,生产数据的线程就是生产者,消费数据的线程就是消费者.


生产者消费者模型的好处

"生产者消费者模型"能给我们的程序带来两个非常重要的好处!!!

实现了发送方和接受方之间的"解耦"

"解耦"就是降低耦合的过程.

我们举一个例子来理解这种模型是怎么解耦的:我们在开发当中,经常会用到服务器之间的相互调用

我们模拟一个对游戏进行充值的场景:

 客户端将充值的操作发送给A服务器,此时A服务器把请求发送给B处理,B处理完了就将结果反馈给A,此时就可以视为"A调用了B"

那么,在上述的场景下,我们可以认为A和B之间的耦合是比较高的,因为A要调用B,那么A势必要知道B的存在,如果B崩溃了,就很容易引起A的bug!

另外,如果要添加一个C服务器,此时也需要对A修改不少的代码,因此就需要对A重新修改代码,重新测试,重新发布部署....这样就非常麻烦了.

针对上述场景,我们采用生产者消费者模型,就可以有效的较低耦合!!!

 我们采用了生产者消费者模型之后,A和B之间的耦合就降低了很多.

A是不知道B的存在,A只知道队列.(A中的代码中没有任何一行代码和B相关)

B也不知道A的存在,B也只知道队列(B的队列中也没有一行代码和A相关)

如果B崩溃了,对于A没有任何影响,因为队列还好着,A仍然可以给队列插入元素.如果队列满了,就阻塞了.

如果A崩溃了,也对于B没有影响,因为队列还好着,B仍然可以从队列中取元素,如果队列空了,也就先阻塞就好了.

A,B任何一方崩溃了都不会对对方造成影响!!!

新增一个C服务器作为消费者,对于A来说也是没有感知的.


"削峰填谷",保证系统的稳定性

如何理解"削峰填谷"?? 我们可以用三峡大坝来类比着理解.

"削峰":如果上游的水多了,三峡大坝就关闸蓄水.此时就相当于三峡大坝承担了上游的冲击,对下游起到了保护作用.

"填谷":如果上游水少了,三峡大坝就开闸放水.有效保证下游的用水情况.

我们进行服务器开发,也和上述模型十分相似!!!

我们的上游就是用户发送的请求,下游就是一些执行具体业务的服务器.

用户在什么时间发送多少请求,这是不可控的,有时候请求多,有时候请求少.如果在某个时间,很多用户都来发送请求,此时如果没有充分的准备,服务器很容易就挂掉.使用"生产者消费者模型"可以有效防止这种情况的发生,从而保证系统的稳定性.

模型代码

public class ThreadDemo22 {
    public static void main(String[] args) {
        BlockingQueue<Integer> blockingQueue = new LinkedBlockingQueue<>();

        //创建两个线程,来作为生产者和消费者
        Thread customer = new Thread(()->{
            while(true){
                try {
                    Integer result = blockingQueue.take();
                    System.out.println("消费元素: "+ result);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        customer.start();

        Thread producer = new Thread(()->{
            int count = 0;
            while (true){
                try {
                    blockingQueue.put(count);
                    System.out.println("生产元素: "+ count);
                    count++;
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        producer.start();
    }
}

 使用标准库提供的阻塞队列

标准库提供的阻塞队列:

 基于链表的阻塞队列;基于数组实现的阻塞队列;带有优先级的阻塞队列

Queue提供的方法有三个:

  1. 入队列 offer
  2. 出队列 poll
  3. 取队首元素 peek

阻塞队列主要的方法有两个:

  1. 入队列 put
  2. 出队列 take

这两个方法是带有阻塞功能的

阻塞队列中也带有offer,poll等方法,但是不具备阻塞功能

 使用阻塞队列:

                                                                                                     

 可以看到队列为空时,再取元素,就发生了阻塞等待,线程一直没有结束.


实现一个阻塞队列

实现一个阻塞队列,首先要实现一个普通的队列.

普通的队列可以基于数组,也可以基于链表,这里我们用环形数组来实现.

入队列时,元素放到tail下标的位置,然后tail++.

出队列时,返回head下标的元素,然后head++.

循环队列势必要涉及到区分队列是满是空的问题:head和tail重合,队列可能是空,也可能是满.

解决方法有两种:

  • 浪费一个元素,当tail+1==head时,就视为队列满了,tail位置不在放置元素.
  • 引入一个size,来记录个数,当size等于数组长度时,就视为队列满了.

先实现一个普通的队列:

 

当前完成了普通队列的实现,加上阻塞功能,就意味着队列要在多线程环境下使用.

为了保证线程的安全,就要加锁,使用synchronized.

 接下来,我们就需要对队列加上阻塞功能.实现阻塞功能,我们使用的时wait和notify关键字来实现.

 那么,这两个线程中的wait是否可能同时触发呢?

(如果同时触发,就不能正确的相互唤醒了)

显然时不能同时触发的,针对同一个队列来说,不能够即是满又是空的.


 上述代码还有一个瑕疵

当wait被唤醒的时候,此时if的条件一定就不成立了吗??

具体来说,put中的wait被唤醒,要求是队列不满,但是wait被唤醒之后,队列一定是不满的吗?

注意:在我们当前代码中,确实不会出现这种情况,当前我们代码的逻辑一定是取元素成功了才唤醒,而且每次取元素都会唤醒put中的wait.但是稳妥起见,最好的办法,是wait之后再次判定一下,看此时的条件是否具备了,然后继续执行下面的放元素的代码.


完整代码

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

class MyBlockingQueue{
     private int[] items = new int[1000];
     private int head = 0;
     private int tail = 0;
     private int size = 0;

    //入队列
    public void put(int value) throws InterruptedException {
        synchronized (this) {
            while (size == items.length){
                //队列满了,此时要产生阻塞
                //return;
                this.wait();
            }
            items[tail] = value;
            tail++;
            //对tail进行处理
            //1)
            //tail = tail % items.length;
            //2)
            if(tail >= items.length){
                tail = 0;
            }
            size++;
            //唤醒take中的wait
            this.notify();
        }
    }
    //出队列
    public Integer take() throws InterruptedException {
        int result = 0;
        synchronized (this) {
            while (size == 0){
                //队列空,也应该要阻塞
                //return null;
                this.wait();
            }
            result = items[head];
            head++;
            if (head >= items.length){
                head = 0;
            }
            size--;
            //唤醒put中的wait
            this.notify();
        }
        return result;
    }


}
public class ThreadDemo23 {
    public static void main(String[] args) throws InterruptedException {
        BlockingQueue<Integer> blockingQueue = new LinkedBlockingQueue<>();

        //创建两个线程,来作为生产者和消费者
        Thread customer = new Thread(()->{
            while(true){
                try {
                    Integer result = blockingQueue.take();
                    System.out.println("消费元素: "+ result);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        customer.start();

        Thread producer = new Thread(()->{
            int count = 0;
            while (true){
                try {
                    blockingQueue.put(count);
                    System.out.println("生产元素: "+ count);
                    count++;
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        producer.start();
    }
}

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值