当前位置:   article > 正文

多线程 — 阻塞队列_多线程阻塞队列

多线程阻塞队列

目录

一、什么是阻塞队列

二、使用阻塞队列

1.put入队列

2.出队列。

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

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

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

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

(2)削峰填谷

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

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

三步实现阻塞队列

1.实现一个队列

 2.加上线程安全

3.加上阻塞功能

编写阻塞队列代码


一、什么是阻塞队列

队列:先进先出

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

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

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

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

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

二、使用阻塞队列

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

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

阻塞队列的核心方法:

1.put入队列

这里入了五个元素。

  1. queue.put("hello1");
  2. queue.put("hello2");
  3. queue.put("hello3");
  4. queue.put("hello4");
  5. queue.put("hello5");

2.出队列。

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

  1. //2.take出队列
  2. String result = null;
  3. result = queue.take();
  4. System.out.println(result);
  5. result = queue.take();
  6. System.out.println(result);
  7. result = queue.take();
  8. System.out.println(result);
  9. result = queue.take();
  10. System.out.println(result);
  11. result = queue.take();
  12. System.out.println(result);
  13. result = queue.take();//发生阻塞,无法打印
  14. 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产生一个元素

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

代码实现如下:

  1. public static void main(String[] args) {
  2. //创建一个阻塞队列
  3. BlockingDeque<Integer> blockingDeque = new LinkedBlockingDeque<>();
  4. //消费者
  5. Thread t1 = new Thread(() -> {
  6. while(true) {
  7. int value = 0;
  8. try {
  9. value = blockingDeque.take();
  10. System.out.println("消费元素:" + value);
  11. } catch (InterruptedException e) {
  12. e.printStackTrace();
  13. }
  14. }
  15. });
  16. t1.start();
  17. //生产者
  18. Thread t2 = new Thread(() -> {
  19. int value = 0;
  20. while (true) {
  21. System.out.println("生产元素:" +value);
  22. try {
  23. blockingDeque.put(value);
  24. value++;
  25. Thread.sleep(1000);
  26. } catch (InterruptedException e) {
  27. e.printStackTrace();
  28. }
  29. }
  30. });
  31. t2.start();
  32. }

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

三步实现阻塞队列:

1.实现一个队列

队列空:head和tail重合

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

 2.加上线程安全

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

3.加上阻塞功能

阻塞机制:

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

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

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

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

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

编写阻塞队列代码

  1. class MyBlockingQueue {
  2. private int[] items = new int[1000];
  3. //约定[head,tail]队列的有效元素
  4. volatile private int head = 0;//指向队首元素的下标
  5. volatile private int tail = 0;//指向队尾元素的下标
  6. volatile private int size = 0;//获取队列种的元素个数
  7. //入队列
  8. synchronized public void put(int elem) throws InterruptedException {
  9. while (size == items.length) {
  10. //队列满了,插入失败
  11. this.wait();//队列满了就要阻塞
  12. }
  13. //把新元素放到tail所在的位置上
  14. items[tail] = elem;
  15. tail++;
  16. //万一tail达到末尾,就需要让tail从头再来,达到一个循环的效果
  17. if (tail == items.length) {
  18. tail = 0;
  19. }
  20. //tail = tail % items.length;//求模也可以表示循环
  21. size++;
  22. this.notify();//唤醒空的队列
  23. }
  24. //出队列
  25. synchronized public Integer take() throws InterruptedException {
  26. while (size == 0) {
  27. //return null;
  28. this.wait();//队列空了就要阻塞
  29. }
  30. int value = items[head];
  31. head++;
  32. if (head == items.length) {
  33. head = 0;
  34. }
  35. size--;
  36. this.notify();//唤醒满的队列
  37. return value;
  38. }
  39. }

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

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小舞很执着/article/detail/968507
推荐阅读
相关标签
  

闽ICP备14008679号