JavaEE之线程(8)_阻塞队列、消费者模型、阻塞队列的实例代码

一、阻塞队列和消费者模型

1.1 什么是阻塞队列?

基本概念:阻塞队列是一种特殊的队列,也遵守 “先进先出” 的原则。
 阻塞队列能是一种线程安全的数据结构, 并且具有以下特性:

  1. 当队列满的时候,继续入队列就会阻塞,直到有其他线程从队列中取走元素。
  2. 当队列空的时候,继续出队列也会阻塞,直到有其他线程往队列中插入元素。

其中,阻塞队列的一个典型应用场景就是 “生产者消费者模型”。 这是一种非常典型的开发模型。

1.2 消费者模型

 生产者、消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题。
 生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取

  1. 阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。
  2. 阻塞队列也能使生产者和消费者之间 解耦

在这里插入图片描述
 比如在 “购物秒杀” 场景下,服务器同一时刻可能会收到大量的支付请求。如果直接处理这些支付请求,服务器可能扛不住(每个支付请求的处理都需要比较复杂的流程)。这个时候就可以把这些请求都放到一个阻塞队列中,然后再由消费者线程慢慢的来处理每个支付请求。
这样做可以有效进行 “削峰”, 防止服务器被突然到来的一波请求直接冲垮。

二、标准库中的阻塞队列

 在 Java 标准库中内置了阻塞队列,我们在日常使用时,可以直接调用标准库中的阻塞队列。其中,

  1. BlockingQueue 是一个接口,真正实现的类是 LinkedBlockingQueue。
  2. put 方法用于阻塞式的入队列, take 用于阻塞式的出队列。
  3. BlockingQueue 也有 offer,poll, peek 等方法,但是这些方法不带有阻塞特性。
BlockingQueue<string> queue = new LinkedBlockingqueue<>();

queue.put("abc");// 入队列

String elem = queue.take();// 出队列.如果没有 put 直接 take,就会阻塞

此外,BlockingQueue还单独扩展了一些特有的方法,内容如下:
在这里插入图片描述

三、阻塞队列的实现(重点)

3.1 通过 “循环队列” 的方式来实现

在这里插入图片描述
 其中,[head, tail) 构成了一个区间,这个区间里的内容就是当前队列中的有效元素。入队列,把新的元素,,放到 tail 位置上,同时 tail++;出队列,把 head 指向的元素给删除掉,head++。
 初始情况下,队列为空的,head 和 tail 重合的;当队列满了的时候,
head 和 tail 又重合。
 解决方案:

  1. 浪费一个位置, 让 tail 指向 head 的前一个位置,就算满了
  2. 专门搞一个变量,size,来表示元素个数,size 为 0 就是 空,为 数组最大值,就是满。
    (更多关于队列的内容见前面的数据结构专栏)

3.2 使用 synchronized 进行加锁控制.

3.3 put 插入元素的时候, 判定如果队列满了, 就进行 wait. (注意, 要在循环中进行 wait,被唤醒时不一定队列就不满了, 因为同时可能是唤醒了多个线程).

阻塞队列put操作的相关实例代码:

public void put(String elem) throws InterruptedException {
        synchronized (locker){
            //1、期望wait返回之后,在判定一次条件
            //2、条件仍然成立,继续wait
            //3、如果不成立,才往后执行
            while (size >= data.length) {

                locker.wait();   //如果队列满了,继续插入元素,就会发生阻塞
            }
            data[tail] = elem; //队列没满,真正的往里面添加元素
            tail++;
            if (tail >= data.length) {
                tail = 0;
            }
            size++;
            locker.notify(); //这个notify,用来唤醒take中的wait
        }
    }

3.4 take 取出元素的时候, 判定如果队列为空, 就进行 wait. (也是循环 wait)

阻塞队列take操作的相关实例代码:

public String take() throws InterruptedException {
        synchronized (locker){
            while (size == 0){
                locker.wait();   //队列空了,如果继续出元素,就会发生阻塞
            }

            //队列不空,就可以将队首元素(head位置的元素)删除掉,并进行返回
            String ret = data[head];
            head++;
            if (head >= data.length){
                head = 0;
            }

            size--;
            locker.notify();  //这个notify 用来唤醒put中的wait
            return ret;
        }
    }

3.5 用阻塞队列实现消费者模型总的示例代码:

package BlockingQueue;

/**
 * @author Zhang
 * @date 2024/5/816:52
 * @Description:
 */
public class MyBlockingQueue {
    // 此处这里的最大长度,也可以指定构造方法,由构造方法的参数来制定
    private String[] data = new String[1000];

    //队列的起始位置
    private volatile int head = 0;
    //队列的结束位置的下一个位置
    private volatile int tail = 0;
    //队列中有效元素的个数
    private volatile int size = 0;

    private final  Object  locker = new Object();

    /**
     * 核心方法,元素入队列
     * @param elem
     * @throws InterruptedException
     */
    public void put(String elem) throws InterruptedException {
        synchronized (locker){
            //1、期望wait返回之后,在判定一次条件
            //2、条件仍然成立,继续wait
            //3、如果不成立,才往后执行
            while (size >= data.length) {

                locker.wait();   //如果队列满了,继续插入元素,就会发生阻塞
            }
            data[tail] = elem; //队列没满,真正的往里面添加元素
            tail++;
            if (tail >= data.length) {
                tail = 0;
            }
            size++;
            locker.notify(); //这个notify,用来唤醒take中的wait
        }
    }

    /**
     * 元素出队列
     * @return
     * @throws InterruptedException
     */
    public String take() throws InterruptedException {
        synchronized (locker){
            while (size == 0){
                locker.wait();   //队列空了,如果继续出元素,就会发生阻塞
            }

            //队列不空,就可以将队首元素(head位置的元素)删除掉,并进行返回
            String ret = data[head];
            head++;
            if (head >= data.length){
                head = 0;
            }

            size--;
            locker.notify();  //这个notify 用来唤醒put中的wait
            return ret;
        }
    }

    public static void main(String[] args) throws InterruptedException {
        MyBlockingQueue queue = new MyBlockingQueue();
        //生产者
        Thread producer = new Thread(()->{
            int num = 0;
            while (true){
                try {
                    queue.put(num+"");
                    System.out.println("生产元素:"+num);
                    num++;
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });

        //消费者
        Thread customer = new Thread(()->{
            while (true){
                try {
                    String result = queue.take();
                    System.out.println("消费元素:"+result);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }

        });
        producer.start();
        customer.start();
       // customer.join();
        //producer.join();
    }
}

总结

 以上就是今天要讲的内容,本文仅仅简单介绍了什么是阻塞队列,生产者消费者模型,并且介绍了标准库中的阻塞队列,以及阻塞队列的具体实现代码。

  • 28
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值