多线程 — 阻塞队列

目录

一、什么是阻塞队列

二、使用阻塞队列

1.put入队列

2.出队列。

 三、“生产者消费者模型”  — 多线程使用阻塞队列模型

1.解释“生产者消费模型”

2.生产者消费模型解决的问题

(1).可以让上下游模块之间,进行更好的"解耦合"

(2)削峰填谷

(3)."生产者消费者模型" —— 阻塞队列代码实现

 四、实现一个自己的阻塞队列(基于数组实现)

三步实现阻塞队列

1.实现一个队列

 2.加上线程安全

3.加上阻塞功能

编写阻塞队列代码


一、什么是阻塞队列

队列:先进先出

阻塞队列:就是带有阻塞特性的队列,主要概念如下:

1.如果队列为空,尝试出队列,就会阻塞等待,等待到队列不为空为止;

2.如果队列未满,尝试入队列,也会阻塞等待,等待到队列不满为止。

3.阻塞队列是线程安全的。

当我们写多线程代码时,多个线程之间进行数据交互,就可以使用阻塞队列简化代码编写。

二、使用阻塞队列

BlockingDeque<String> queue = new LinkedBlockingDeque<>();

这里的BlockingDeque是一个接口,所以不能直接new。

阻塞队列的核心方法:

1.put入队列

这里入了五个元素。

queue.put("hello1");
queue.put("hello2");
queue.put("hello3");
queue.put("hello4");
queue.put("hello5");

2.出队列。

将前面五个元素出队列,当前面五个队列都出去了,队列为空了,此时队列发生阻塞。

 //2.take出队列
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);

 三、“生产者消费者模型”  — 多线程使用阻塞队列模型

1.解释“生产者消费模型”

 关于这个模型,举例说明如下:

假如有几个人在包饺子,这几个人发现,擀面杖只有一个,如果每个人边擀面边包饺子太慢,还要争抢擀面杖,于是他们就做了如下改动:

此时,我们就将1号比喻为生产者(产生资源),包饺子的另外几个人就是消费者(消耗资源),这个盖帘就是阻塞队列(放置资源)。

所以结合阻塞队列的特性:

盖帘放不下了(队列满了),1号就暂时不放了(阻塞等待,就暂时不入队列了);

盖帘空了(队列空了),包饺子的人就可以歇息(阻塞等待,暂时不出队列了)。

2.生产者消费模型解决的问题

(1).可以让上下游模块之间,进行更好的"解耦合"

高耦合:联系紧密,关系紧密,如果两者之间符合高耦合,乙方有什么变化,可能就会影响到另一方。低耦合则是与之相反。

内聚:就是指有关联的东西放在一起。

考虑到以下场景:

A服务器调用B服务器:A给B发送请求,B给A返回响应,两个服务器正在进行正常交互,两者之间属于高耦合的情况,如果A或者B出现问题,就会对双方出现影响。

 然后此时来了一个C,也要于A进行交互,

 此时A如果要与C进行交互,A就需要做一个很大的调整,但此时就会影响到B,针对以上的问题我们就需要引入“生产者消费者模型”,用到阻塞队列。

 此时A,B,C相互不知道对方的存在,如果需要交互资源,直接通过阻塞队列服务器获取即可,相互不会受到影响,起到了一个“解耦合”的效果。

关于阻塞队列服务器,其实也有可能出问题,但是比ABC出现问题的概率要低,因为我们做业务,一般实在ABC里面进行,容易出bug,而不用阻塞队列,所以阻塞队列不容易出问题。

(2)削峰填谷

举例以下情况,用户发出请求,AB两个服务器是相互调用的关系,如下图,

 峰值:比如A平时受到1万/秒的请求,然后突然出现了3万/秒的请求,这种情况就会出现峰值。

如果A出现峰值,那么B也会出现峰值。如果此时B没有考虑峰值的处理,由于服务器处理每个请求,都要消耗一定的硬件资源(CPU,内存......),那么这个B就会出现问题,这种情况,就会给系统的稳定性带来一定的风险。

所以以上直接调用的方式就会给系统稳定性带来风险,所以就需要运用“阻塞队列”进行“削峰填谷”。

如下图,

 虽然A收到的请求多了,但由于每次是A,B之间不是直接交互,有了阻塞队列,就是阻塞队列里面的元素增多了,但是B还是按照原来的速率从阻塞队列里拿取请求,不会受到影响,阻塞队列帮助B承担了压力,此过程即为“削峰”。

当峰值过去之后,还有一个波谷,但是由于B有了阻塞队列,也就不用改变畜栏里请求的速率,仍按照原来的速率消耗阻塞队列中的元素,此过程即为“填谷”。

(3)."生产者消费者模型" —— 阻塞队列代码实现

代码作用:

生产者:每隔1s产生一个元素

消费者:直接消费,不受限制。

代码实现如下:

public static void main(String[] args) {
        //创建一个阻塞队列
        BlockingDeque<Integer> blockingDeque = new LinkedBlockingDeque<>();

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

 四、实现一个自己的阻塞队列(基于数组实现)

三步实现阻塞队列:

1.实现一个队列

队列空:head和tail重合

队列满:当tail < head时,就视为队列满

 2.加上线程安全

基于队列有很多的读入和修改操作,此时我们需要使用synchronized和volatile关键字。

3.加上阻塞功能

阻塞机制:

(1)队列满了就wait(),然后队列有元素出去了,不满了,就让notify()唤醒;

(2)队列空了就wait(),当队列增加了元素也让notify()唤醒。

此处有一个注意点,wait()有可能会被提前唤醒,很多方法,比如使用interrupt,就会把wait()提前还行,但是此时可能条件还没满足(还没满或者还没非空),wait就直接被唤醒往下走了,就有可能会出现问题。

所以我们就需要在wait唤醒以后,再加一个判定条件:

wait()之前,发现条件不满足,开始wait();然后等到wait()被唤醒了以后,再确认以下这个条件是不是满足的,如果不满足,还是再继续wait()

编写阻塞队列代码

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) {
            //队列满了,插入失败
            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;
    }
}

阻塞队列到这里就结束了,求一键三连啦~~

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值