多线程案例二:阻塞队列

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言(中间件,消息队列)

队列科普延伸

并不是所有的队列都是先进先出的:
优先级队列priorityQueue,根据优先级
消息队列:在队列的元素中,引入一个类型“topic”(业务上的类型),入队列没事,出队列的时候,会指定某个类型的元素,先出
消息队列在日常开发应用:
工作中经常把消息队列这样的数据结构,单独实现成一个程序,并且部署在一组服务器上
消息队列服务器,也就是咱们平时说的MQ,也就是一种常用的“”中间件“”

中间件就是一类通用的服务器的统称,例如mysql可以视为一个中间件,因为不同业务对数据存储需求差不多。

正文

一.阻塞队列是什么

是一个特殊队列,先进先出的

特点

  • 线程安全
  • 带有阻塞功能
    a)如果队列满,继续入队列,入队列操作就会阻塞,直到队列不满,入队列才能完成
    b)如果队列空,继续出队列,出队列操作也会阻塞,直到队列不空,出队列才能完成

应用场景:

生产者消费者模型:描述的是多线程协同工作的一种方式

优点:

1.使用阻塞队列,有利于代码解耦合
耦合:俩个模块之间的关联关系,关系越紧密,耦合越高,关系越不紧密,耦合越低
在这里插入图片描述
2.削峰填谷:
按照没有生产者消费者模型的写法,外面流量过来的压力,就会直接压倒在每个服务器上,如果摸个服务器的抗压能力不太行,就容易挂
使用阻塞队列,流量骤增时,a和队列承受了压力,b,c还是按照原来的节奏来消费数据,对于b,c冲击不大
类似于三峡水库:
在这里插入图片描述

二、阻塞队列的具体使用:

1.标准库

//系统实现的阻塞队列
public class demo22 {
    public static void main(String[] args) throws InterruptedException {
        BlockingDeque<Integer> blockingDeque = new LinkedBlockingDeque<>(100);
        //        //带有阻塞功能的入队列
        blockingDeque.put(1);
        blockingDeque.put(2);
        blockingDeque.put(3);

        Integer ret = blockingDeque.take();
        System.out.println(ret);
        ret = blockingDeque.take();
        System.out.println(ret);
        ret = blockingDeque.take();
        System.out.println(ret);

        ret = blockingDeque.take();
        System.out.println(ret);
    }
    }

2.自己实现(入队,出队)

思路:

  • 先实现一个普通队列(基于链表,基于数组)(循环队列)
    首先记录下队首和队尾元素的位置:
    在这里插入图片描述
    [head,tail)表示队列中有效元素的范围
    如何区分队列空还是满:加一个变量size记录个数
    当tail走向尾时候,再插入元素先判满,不满,可以将tail置为0,当size=item.length时候队列满
    在这里插入图片描述

  • 加上线程安全
    保证线程安全,关键就是加锁

  • 加上阻塞的实现
    如果队列空,出队列阻塞
    如果队列为满,入队列阻塞

    在这里插入图片描述
    在这里插入图片描述
    注意在同一个队列同一时刻,不会出现又空又满(阻塞条件相悖)

三.完整代码

class MyBlockingQueue {
    private int[] items = new int[1000];
    private volatile int head = 0;
    private volatile int tail = 0;
    private volatile int size = 0;
//入队列
public void put(int elem) throws InterruptedException {
    synchronized (this) {
        //判断队列是否满了,满了则不能插入
        while (size >= items.length) {
            this.wait();
        }
        //进行插入操作,把elem放到items里,放到tail指向的位置
        items[tail] = elem;
        tail++;
        if (tail >= items.length) {
            tail = 0;
        }
        size++;
        this.notify();
    }
}

//出队列,返回删除的元素的内容
public Integer take() throws InterruptedException {
    synchronized (this) {
        while (size == 0) {
            this.wait();
        }
        //进行取元素操作
        int ret = items[head];
        head++;
        if (head >= items.length) {
            head = 0;
        }
        size--;
        this.notify();
        return ret;
    }
}

实现细节

关于循环语句为什么是while,不是if
假设线程要进行取操作,当队列为空,take进入wait状态,具体啥时候唤醒是不确定的,当wait唤醒队列不一定非空,要进行多次判断(线程的抢占式执行)
1.wait被interrupt提前唤醒(没有继续等待put操作,导致队列依然是空,非法引用
2.多个线程的调度:
在这里插入图片描述
t1线程执行take操作,t2,t3竞争锁,t2线程拿到锁,插入元素,t1,t3竞争锁,t3拿到锁take走元素,此时t1再拿到锁时候,队列依然是空,要进行多组判断,

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值