当前位置:   article > 正文

JUC高并发编程(14) -- 阻塞队列_并发编程阻塞队列

并发编程阻塞队列

JUC高并发编程

10.1)BlockingQueue 简介

Concurrent包中,BlockingQueue很好的解决了多线程中,如何高效安全“传输”数据的问题;通过这些高效并且线程安全的队列类,为我们快速搭建高质量的多线程程序带来极大的便利。

阻塞队列,顾名思义,首先它是一个队列, 通过一个共享的队列,可以使得数据由队列的一端输入,从另外一端输出,如下图所示:

 阻塞情况如下:

  • 当队列是空的,从队列中获取元素的操作将会被阻塞;

  • 当队列是满的,从队列中添加元素的操作将会被阻塞;

  • 试图从空的队列中获取元素的线程将会被阻塞,直到其他线程往空的队列插入新的元素;

  • 试图向已满的队列中添加新元素的线程将会被阻塞,直到其他线程从队列中移除一个或多个元素或者完全清空,使队列变得空闲起来并后续新增

10.1.1)常用的队列

常用的队列主要有以下两种:

  • 先进先出(FIFO):先插入的队列的元素也最先出队列,类似于排队的功能,从某种程度上来说这种队列也体现了一种公平性 (队列)

  • 后进先出(LIFO):后插入队列的元素最先出队列,这种队列优先处理最近发生的事件(栈)

10.1.2)为什么需要BlockingQueue

在concurrent包发布以前,在多线程环境下,开发者都必须去自己控制阻塞线程的细节,尤其还要兼顾效率和线程安全,而这会给我们的程序带来不小的复杂度;

使用BlockingQueue 之后,开发者不需要关心什么时候需要阻塞线程,什么时候需要唤醒线程,因为这一切 都由BlockingQueue一手包办了。

10.1.3)BlockingQueue的实际应用

多线程环境中,通过队列可以很容易实现数据共享,比如经典的“生产者”和 “消费者”模型中,通过队列可以很便利地实现两者之间的数据共享。

假设有若干生产者线程,另外又有若干个消费者线程。如果生产者线程需要把准备好的数据共享给消费者线程,利用队列的方式来传递数据,就可以很方便地解决它们之间的数据共享问题。

但如果生产者和消费者在某个时间段内,万一发生数据处理速度不匹配的情况如何解决呢?

  1. 理想情况下,如果生产者产出数据的速度大于消费者消费的速度,并且当生产出来的数据累积到一定程度的时候,那么生产者必须暂停等待一下(阻塞生产者线程),以便等待消费者线程把累积的 数据处理完毕,反之亦然;

  2. 当队列中没有数据的情况下,消费者端的所有线程都会被自动阻塞(挂起), 直到有数据放入队列;

  3. 当队列中填满数据的情况下,生产者端的所有线程都会被自动阻塞(挂起), 直到队列中有空的位置,线程被自动唤醒

10.2)BlockingQueue 核心方法

BlockingQueue的核心方法,如下图所示:

 10.2.1)放入数据

  1. offer(anObject):表示如果可能的话,将anObject加到BlockingQueue 里,即如果BlockingQueue可以容纳,则返回true,否则返回false;本方法不阻塞当前执行方法的线程)

  2. offer(E o, long timeout, TimeUnit unit):可以设定等待的时间,如果在指定的时间内,还不能往队列中加入BlockingQueue,则返回失败

  3. put(anObject):把anObject加到BlockingQueue 里,如果BlockQueue没有空间,则调用此方法的线程被阻断直到BlockingQueue里面有空间再继续

10.2.2)获取数据

  1. poll(time): 取走BlockingQueue里排在首位的对象,若不能立即取出,则可以等time参数规定的时间,还取不到时返回null;

  2. poll(long timeout, TimeUnit unit):从BlockingQueue取出一个队首的对象, 如果在指定时间内,队列一旦有数据可取则立即返回队列中的数据;否则直到时间超时还没有数据可取则返回失败。

  3. take():取走BlockingQueue 里排在首位的对象,若BlockingQueue为空,阻断进入等待状态直到BlockingQueue有新的数据被加入;

  4. drainTo(): 一次性从BlockingQueue获取所有可用的数据对象(还可以指定 获取数据的个数),通过该方法可以提升获取数据效率,不需要多次分批加锁或释放锁。

10.3)常见的 BlockingQueue

10.3.1) ArrayBlockingQueue(常用)

基于数组的阻塞队列实现,在ArrayBlockingQueue内部,维护了一个定长数组以便缓存队列中的数据对象,这是一个常用的阻塞队列,除了一个定长数组外,ArrayBlockingQueue 内部还保存着两个整形变量,分别标识着队列的头部和尾部在数组中的位置。

ArrayBlockingQueue在生产者放入数据和消费者获取数据,都是共用同一个锁对象,由此也意味着两者无法真正并行运行,这点尤其不同于 LinkedBlockingQueue;按照实现原理来分析,ArrayBlockingQueue 完全可以采用分离锁,从而实现生产者和消费者操作的完全并行运行。

Doug Lea之 所以没这样去做,也许是因为ArrayBlockingQueue的数据写入和获取操作已经足够轻巧,以至于引入独立的锁机制,除了给代码带来额外的复杂性外,其在性能上完全占不到任何便宜。

ArrayBlockingQueue和 LinkedBlockingQueue 间还有一个明显的不同之处在于,前者在插入或删除元素时不会产生或销毁任何额外的对象实例,而后者则会生成一个额外的Node对象。

这在长时间内需要高效并发地处理大批量数据的系统中,其对于 GC的影响还是存在一定的区别,而在创建ArrayBlockingQueue时,还可以控制对象的内部锁是否采用公平锁,默认采用非公平锁。

总结:由数组结构组成的有界阻塞队列

10.3.2)LinkedBlockingQueue(常用)

基于链表的阻塞队列,同ArrayListBlockingQueue 类似,其内部也维持着一个数据缓冲队列(该队列由一个链表构成),当生产者往队列中放入一个数据时,队列会从生产者手中获取数据,并缓存在队列内部,而生产者立即返回;

只有当队列缓冲区达到最大值缓存容量时(LinkedBlockingQueue可以通过构造函数指定该值),才会阻塞生产者队列,直到消费者从队列中消费掉一份数据,生产者线程会被唤醒,反之对于消费者这端的处理也基于同样的原理。

而LinkedBlockingQueue 之所以能够高效的处理并发数据,还因为其对于生产者端和消费者端分别采用了独立的锁来控制数据同步,这也意味着在高并发的情况下生产者和消费者可以并行地操作队列中的数据,以此来提高整个队列的并发性能。

总结:由链表结构组成的有界(但大小默认值为 integer.MAX_VALUE)阻塞队列

10.3.3)DelayQueue

DelayQueue中的元素只有当其指定的延迟时间到了,才能够从队列中获取到该元素。

DelayQueue是一个没有大小限制的队列,因此往队列中插入数据的操作(生产者)永远不会被阻塞,而只有获取数据的操作(消费者)才会被阻塞。

总结: 使用优先级队列实现的延迟无界阻塞队列

10.3.4) PriorityBlockingQueue

基于优先级的阻塞队列(优先级的判断通过构造函数传入的Compator对象来决定),但需要注意的是PriorityBlockingQueue并不会阻塞数据生产者,而只会在没有可消费的数据时,阻塞数据的消费者。

因此使用的时候要特别注意,生产者生产数据的速度绝对不能快于消费者消费 数据的速度,否则时间一长,会最终耗尽所有的可用堆内存空间。

在实现PriorityBlockingQueue 时,内部控制线程同步的锁采用的是公平锁。

总结: 支持优先级排序的无界阻塞队列

10.4)ArrayBlockingQueue案例

10.4.1) add && remove

ArrayBlockingQueue的 add && remove案例展示代码如下:

  1. //阻塞队列
  2. public class BlockingQueueDemo1 {
  3.    public static void main(String[] args) throws InterruptedException {
  4.        //创建阻塞队列
  5.        BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);
  6.        //第一组: add && remove
  7.        System.out.println(blockingQueue.add("a"));
  8.        System.out.println(blockingQueue.add("b"));
  9.        System.out.println(blockingQueue.add("c"));
  10.        // System.out.println(blockingQueue.element());
  11.          // 队列已满,再添加元素会报错
  12.        System.out.println(blockingQueue.add("w"));
  13.        System.out.println(blockingQueue.remove());
  14.        System.out.println(blockingQueue.remove());
  15.        System.out.println(blockingQueue.remove());
  16.           // 队列已空,再删除元素会报错
  17.        System.out.println(blockingQueue.remove());
  18.   }
  19. }

输出:队列已满,再添加元素会报错

Exception in thread "main" java.lang.IllegalStateException: Queue full
    at java.util.AbstractQueue.add(AbstractQueue.java:98)
    at java.util.concurrent.ArrayBlockingQueue.add(ArrayBlockingQueue.java:312)
    at com.study.queue.BlockingQueueDemo1.main(BlockingQueueDemo1.java:19)


  1. //阻塞队列
  2. public class BlockingQueueDemo1 {
  3.    public static void main(String[] args) throws InterruptedException {
  4.        //创建阻塞队列
  5.        BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);
  6.        //第一组: add && remove
  7.        System.out.println(blockingQueue.add("a"));
  8.        System.out.println(blockingQueue.add("b"));
  9.        System.out.println(blockingQueue.add("c"));
  10.        System.out.println(blockingQueue.element());
  11.          // 队列已满,再添加元素会报错
  12.       // System.out.println(blockingQueue.add("w"));
  13.        System.out.println(blockingQueue.remove());
  14.        System.out.println(blockingQueue.remove());
  15.        System.out.println(blockingQueue.remove());
  16.           // 队列已空,再删除元素会报错
  17.        System.out.println(blockingQueue.remove());
  18.   }
  19. }

输出:队列已空,再删除元素会报错

Exception in thread "main" java.util.NoSuchElementException
    at java.util.AbstractQueue.remove(AbstractQueue.java:117)
    at com.study.queue.BlockingQueueDemo1.main(BlockingQueueDemo1.java:22)


  1. //阻塞队列
  2. public class BlockingQueueDemo1 {
  3.    public static void main(String[] args) throws InterruptedException {
  4.        //创建阻塞队列
  5.        BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);
  6.        //第一组: add && remove
  7.        System.out.println(blockingQueue.add("a"));
  8.        System.out.println(blockingQueue.add("b"));
  9.        System.out.println(blockingQueue.add("c"));
  10.        System.out.println(blockingQueue.element());
  11.          // 队列已满,再添加元素会报错
  12.        // System.out.println(blockingQueue.add("w"));
  13.        System.out.println(blockingQueue.remove());
  14.        System.out.println(blockingQueue.remove());
  15.        System.out.println(blockingQueue.remove());
  16.           // 队列已空,再删除元素会报错
  17.        // System.out.println(blockingQueue.remove());
  18.   }
  19. }

输出:

true
true
true
a
a
b
c

10.4.2)offer && poll

ArrayBlockingQueue的 offer && poll 案例展示代码如下:

  1. //阻塞队列
  2. public class BlockingQueueDemo2 {
  3.    public static void main(String[] args) throws InterruptedException {
  4.        //创建阻塞队列
  5.        BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);
  6.        //第二组:offer && poll
  7.        System.out.println(blockingQueue.offer("a"));
  8.        System.out.println(blockingQueue.offer("b"));
  9.        System.out.println(blockingQueue.offer("c"));
  10.        // 输出: false
  11.        System.out.println(blockingQueue.offer("www"));
  12.        System.out.println(blockingQueue.poll());
  13.        System.out.println(blockingQueue.poll());
  14.        System.out.println(blockingQueue.poll());
  15.        // 输出:null
  16.        System.out.println(blockingQueue.poll());
  17.   }
  18. }

输出:

true
true
true
false
a
b
c
null

10.4.3)put && take

ArrayBlockingQueue的 put && take 案例展示代码如下:

  1. //阻塞队列
  2. public class BlockingQueueDemo3 {
  3.    public static void main(String[] args) throws InterruptedException {
  4.        //创建阻塞队列
  5.        BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);
  6.        //第三组:put && take
  7.        blockingQueue.put("a");
  8.        blockingQueue.put("b");
  9.        blockingQueue.put("c");
  10.        // 输出:程序一直未结束,因为空间不够,所以处于阻塞状态,直至将数据插入才结束
  11.        //blockingQueue.put("w");
  12.        System.out.println(blockingQueue.take());
  13.        System.out.println(blockingQueue.take());
  14.        System.out.println(blockingQueue.take());
  15.        // 输出:程序一直未结束,因为队列为空没有取到元素,所以处于阻塞状态,直至将数据取到才结束
  16. //       System.out.println(blockingQueue.take());
  17.   }
  18. }

输出:

a

b

c

10.4.4)offer

ArrayBlockingQueue的offer方法案例展示代码如下:

  1. //阻塞队列
  2. public class BlockingQueueDemo4 {
  3.    public static void main(String[] args) throws InterruptedException {
  4.        //创建阻塞队列
  5.        BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);
  6.        //第四组 :offer
  7.        System.out.println(blockingQueue.offer("a"));
  8.        System.out.println(blockingQueue.offer("b"));
  9.        System.out.println(blockingQueue.offer("c"));
  10.        // 输出:false
  11.        System.out.println(blockingQueue.offer("w",3L, TimeUnit.SECONDS));
  12.   }
  13. }

输出:

true
true
true

等待3秒后...

false

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

闽ICP备14008679号