也许你以前学习数据结构时,接触过Queue(队列),它具有先进先出的特性。
什么是阻塞队列 BlockingQueue ?
顾名思义它一个队列,具有阻塞功能。
为什么要学习阻塞队列?
- 很多朋友可能有些陌生,其实只要使用过线程池,就表示你已经间接地使用过阻塞队列了,阻塞队列是线程池的重要组成部分,后面文章会详细介绍线程池。
- 阻塞队列是线程安全的,所以阻塞队列可以用作线程安全的并发容器,比如生产者消费者模式。
1、常见的阻塞队列
阻塞队列关系图
1.1 ArrayBlockingQueue
由数组结构组成的有界阻塞队,需要指定队列容量,可指定公平策略:如果是公平的策略,那么等待了最长时间的线程会优先被处理,不过这会同时带来一定的性能损耗
1.2 LinkedBlockingQueue
由链表结构组成的有界阻塞队列,如果不指定容量,默认为Integer.MAX_VALUE
。
内部结构:Node
两把锁Lock:takeLock、putLock
两个条件Condition:notEmpty、notFull
1.3 PriorityBlockingQueue
使用优先级队列实现的无界阻塞队列,支持优先级,自然顺序(而不是先进先出),是 priorityQueue的线程安全版本
1.4 SynchronousQueue
一个不存储元素的阻塞队列,它的容量为 0
,每一个put操作都要等待一个take操作。
注意:
- SynchronousQueue 的容量不是1而是0,因为 SynchronousQueue 不需要去持有元素,它所做的就是直接传递(direct handoff)
- SynchronousQueue 没有 peek等函数,因为 peek 的含义是取出头节点,但是 SynchronousQueue 的容量为 0,所以连头节点都没有,也就没有 peek 方法。同理,没有 iterate 相关方法。
- 是一个极好的用来直接传递的并发数据结构
- SynchronousQueue 是线程池 Executors.newCachedThreadPool() 使用的阻塞队列。
1.5 DelayQueue
使用优先级队列实现的无界阻塞队列,支持延时获取的元素的阻塞队列,元素必须要实现Delayed接口,获取元素剩余的延迟时间
适用场景:订单到期,限时支付等等
2、阻塞队列常用方法
方法 | 描述 |
---|---|
void put(E e) | 如果有空间,队尾插入数据; 如果队列已满,则无法插入,阻塞,直到有空间 |
boolean add(E e) | 如果有空间,队尾插入数据,返回true; 如果队列已满,则抛出 IllegalStateException |
boolean offer(E e) | 如果有空间,队尾插入数据,返回true; 如果队列已满,则返回false |
boolean offer (E e, long timeout, TimeUnit unit) |
如果有空间,队尾插入数据,返回true; 如果队列已满,等待指定的等待时间,若超时仍未有空间,则返回false |
E take() | 获取并删除队头节点, 如果队列无数据,则阻塞,直到有数据 |
boolean remove() | 获取并删除队头节点, 如果队列无数据,则抛出 NoSuchElementException |
E poll() | 获取并删除队头节点, 如果队列无数据,则返回 null |
E poll(long timeout, TimeUnit unit) |
获取并删除队头节点, 如果队列无数据,等待指定的等待时间,若超时仍未有数据,则返回 null |
E peek() | 获取但是不删除队头节点,如果队列无数据,返回null |
E element() | 获取但是不删除队头节点,如果队列无数据,抛出 NoSuchElementException |
3、ArrayBlockingQueue 的使用和源码
下面以生产者消费者案例为场景,演示 ArrayBlockingQueue 阻塞队列的使用。
3.1 生产者和消费者模式
生产者就是生产数据的线程,消费者就是消费数据的线程。
在多线程开发中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理完,才能继续生产数据。同样的道理,如果消费者的处理能力大于生产者,那么消费者就必须等待生产者。为了解决这种生产消费能力不均衡的问题,便有了生产者和消费者模式。
生产者和消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。
生产者和消费者彼此之间不直接通信,而是通过阻塞队列来进行通信,生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。
ArrayBlockingQueue实现生产者和消费者模式
public class ArrayBlockingQueueDemo {
//用阻塞队列作为容器,指定容量为5
private ArrayBlockingQueue<Goods> blockingQueue = new ArrayBlockingQueue<>(5);
// 全局商品索引
static int goodsIndex;
// 打印日志的lock,是公平锁,等待时间长的线程优先拿到锁
private Lock fairLock = new ReentrantLock(true);
public static void main(String[] args) {
ArrayBlockingQueueDemo demo = new ArrayBlockingQueueDemo();
for (int i = 0; i < 3; i++) {
Producer producer = new Producer(demo);
new Thread(producer, "生产者P" + (i + 1)).start();
}
for (int i = 0; i < 2; i++) {
Consumer consumer = new Consumer(demo);
new Thread(consumer, "消费者C" + (i + 1)).start();