java并发包简述之BlockingQueue

BlockingQueue

java并发包下提供的阻塞队列

BlockingQueue同时支持两个特性:

1.取数据的时候如果发现队列没有数据,则等待队列变成非空;

2.存数据的时候,如果发现队列已满,则等待队列有空闲空间;

对于这个问题的理解,我们可以先简单的用使用java实现生产者消费者模型时,如果队列已满,生产者就会wait在缓冲区对象上等待消费者唤醒,而如果队列为空,消费者就会wait在缓冲队列上等待生产者唤醒,BlockingQueue就是这么一个替我们实现了生产者消费者模型的队列;

方法返回值的约定

一般来说,如果方法操作不能立即返回,而是需要等待队列,则可以选择以下四种模式其一作为返回:

  1. 抛出异常;
  2. 返回特殊值(null或者false);
  3. 无限制堵塞,直到操作成功或失败;
  4. 堵塞指定时间,超时放弃操作;
对元素的约定

BlockingQueue一般不接受null元素

线程安全性问题

BlockingQueue的具体实现是需要保证线程安全性的

add()、offer()和put()的区别
方法名 特点
add() 在队列满的情况下,直接抛出异常
offer() 在队列满的情况下,根据方法返回值的约定返回对应的值
put() 在队列满的情况下,等待队列有足够的空间
poll()、take()、peek()和remove()的区别
方法名 特点
poll() 检索并删除队列里的首个元素,如果队列为空返回null
peek() 检索但不删除队列里的首个元素,如果队列为空返回null
take() 检索并删除队列里的首个元素,如果队列为空堵塞到队列不为空
remove() 检索并删除队列里的指定元素

ArrayBlockingQueue

这是一个由数组实现的有界堵塞队列。对元素进行FIFO(先进先出)排序。队列的头是在队列中出现时间最长的元素。队列的尾部是在队列中出现的时间最短的元素。新元素插入队列尾部,队列检索操作获取队列头部的元素。创建后容量将无法更改。

该队列支持公平和非公平策略,底层是交给ReentrantLock公平和非公平机制实现,该队列从一个ReentrantLock中获取了两个Condition,用与区分写锁和读锁。

入队的时候唤醒读锁,出队的时候唤醒写锁。所以读写不能并发

由于数组的特性,ArrayBlockingQueue维护了一个读下标和写下标,在入队和出队的时候可以直接操作对应下标来获取元素。

另外额外维护了一个count,记录队列元素的数量。

LinkedBlockingQueue

使用链表结构实现的有界堵塞队列,对元素进行FIFO(先进先出)排序。相对于ArrayBlockingQueue来说,内部维护了两个ReentrantLock,分别维护读锁和写锁,实现了读写并发,链表的首位节点保持为null;

DelayQueue

支持延时获取的无界阻塞队列,元素全都是支持延时获取的;元素通过实现Delayed接口,通过getDelay()方法得到剩余的延迟;
读写使用同一个ReentrantLock,不能同时操作读写;
通过内部维护的Thread变量,在每次offer()的时候置空变量;
在每次获取元素的时候,如果队列里没有元素,或者队列里的元素还没到延期的时间,就会指定Thread变量为当前线程并通过线程等待元素到期,在返回时同样也会把Thread置空;

PriorityBlockingQueue

支持优先级的无界阻塞队列,不允许元素为null,且元素必须要实现了Comparable接口,用来支持排序,队列的排序是从小到大的;
使用最小二叉堆和数组实现元素的存储;
读写锁由同一把ReentrantLock控制;在扩容的时候使用allocationSpinLock变量来做CAS自旋锁,避免在扩容时take函数的堵塞;

SynchronousQueue

双栈双队列,本身没有任何空间,入队列操作必须等待一个出队列操作,这也就意味着本身没有可迭代的数据,以及规定了元素不允许为null;
这个队列特别适合需要线程切换的设计,一个线程运行的对象必须与另一个线程的对象进行信息交换,或者产生事件、发起任务(in order to hand it some information, event, or task.)
双队列里要么有数据,要么都是null;在双队列模式下,所有操作都可以判断到底是否存在数据并执行自己的对应操作,无需进行任何锁定;

Transferer

SynchronousQueue的内部抽象类,提供了一个方法transfer(),并且有两个实现TransferStack和TransferQueue分别实现了双栈和双队列模式。