JAVA中常见的阻塞队列详解


JAVA中常见的阻塞队列详解

文章插图
  • 在之前的线程池的介绍中我们看到了很多阻塞队列,这篇文章我们主要来说说阻塞队列的事 。
  • 阻塞队列也就是 BlockingQueue ,这个类是一个接
  • 口,同时继承了 Queue 接口,这两个接口都是在JDK5 中加入的。
  • BlockingQueue 阻塞队列是线程安全的,在我们业务中是会经常频繁使用到的,如典型的生产者消费的场景,生产者只需要向队列中添加,而消费者负责从队列中获取 。

JAVA中常见的阻塞队列详解

文章插图
  • 如上图展示,我们生产者线程不断的put 元素到队列,而消费者从中take 出元素处理,这样实现了任务与执行任务类之间的解耦,任务都被放入到了阻塞队列中,这样生产者和消费者之间就不会直接相互访问实现了隔离提高了安全性 。
并发队列
JAVA中常见的阻塞队列详解

文章插图
  • 上面是 JAVA 中队列Queue 类的类图,我们可以看到它分为两大类,阻塞队列与非阻塞队列
  • 阻塞队列的实现接口是 BlockingQueue 而非阻塞队列的接口是 ConcurrentLinkedQueue , 本文主要介绍阻塞队列,非阻塞队列不再过多阐述
  • BlockingQueue 主要有下面六个实现类,分别是 ArrayBlockingQueueLinkedBlockingQueueSynchronousQueueDelayQueuePriorityBlockingQueueLinkedTransferQueue  。这些阻塞队列有着各自的特点和适用场景,后面详细介绍 。
  • 非阻塞队列的典型例子如 ConcurrentLinkedQueue , 它不会阻塞线程,而是利用了 CAS 来保证线程的安全 。
  • 其实还有一个队列和 Queue 关系很紧密,那就是Deque,这其实是 double-ended-queue 的缩写,意思是双端队列 。它的特点是从头部和尾部都能添加和删除元素,而我们常见的普通队列Queue 则是只能一端进一端出,即FIFO  。
阻塞队列特点
  • 阻塞队列的特点就在于阻塞,它可以阻塞线程,让生产者消费者得以平衡,阻塞队列中有两个关键方法 Put 和 Take 方法
take方法
  • take方法的功能是获取并移除队列的头结点,通常在队列里有数据的时候是可以正常移除的 。可是一旦执行 take 方法的时候,队列里无数据,则阻塞,直到队列里有数据 。一旦队列里有数据了,就会立刻解除阻塞状态,并且取到数据 。过程如图所示:

JAVA中常见的阻塞队列详解

文章插图
put方法
  • put方法插入元素时,如果队列没有满,那就和普通的插入一样是正常的插入,但是如果队列已满,那么就无法继续插入,则阻塞,直到队列里有了空闲空间 。如果后续队列有了空闲空间,比如消费者消费了一个元素,那么此时队列就会解除阻塞状态,并把需要添加的数据添加到队列中 。过程如图所示:

JAVA中常见的阻塞队列详解

文章插图
是否有界(容量有多大)
  • 此外,阻塞队列还有一个非常重要的属性,那就是容量的大小,分为有界和无界两种 。
  • 无界队列意味着里面可以容纳非常多的元素,例如 LinkedBlockingQueue的上限是 Integer.MAX_VALUE,约为 2 的 31 次方,是非常大的一个数,可以近似认为是无限容量,因为我们几乎无法把这个容量装满 。
  • 但是有的阻塞队列是有界的,例如 ArrayBlockingQueue如果容量满了,也不会扩容,所以一旦满了就无法再往里放数据了 。
阻塞队列常见方法
  • 首先我们从常用的方法出发,根据各自的特点我们可以大致分为三个大类,如下表所示:
分类方法含义特点 抛出异常add添加一个元素如果队列已满,添加则抛出 IllegalStateException 异常 remove删除队列头节点当队列为空后,删除则抛出 NoSuchElementException 异常 element获取队列头元素当队列为空时,则抛出 NoSuchElementException


推荐阅读