BlockingQueue
java并发包下提供的阻塞队列
BlockingQueue同时支持两个特性:
1.取数据的时候如果发现队列没有数据,则等待队列变成非空;
2.存数据的时候,如果发现队列已满,则等待队列有空闲空间;
对于这个问题的理解,我们可以先简单的用使用java实现生产者消费者模型时,如果队列已满,生产者就会wait在缓冲区对象上等待消费者唤醒,而如果队列为空,消费者就会wait在缓冲队列上等待生产者唤醒,BlockingQueue就是这么一个替我们实现了生产者消费者模型的队列;
方法返回值的约定
一般来说,如果方法操作不能立即返回,而是需要等待队列,则可以选择以下四种模式其一作为返回:
- 抛出异常;
- 返回特殊值(null或者false);
- 无限制堵塞,直到操作成功或失败;
- 堵塞指定时间,超时放弃操作;
对元素的约定
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分别实现了双栈和双队列模式。