阻塞队列指的是当队列已满的时候,入队操作阻塞,而当队列为空的时候,出队操作阻塞。同时,在种类的区分的时候还区分单端和双端区别,单端就是以 Queue 为名的队列,而双端是以 Deque 为名的队列。
阻塞队列实现的接口 BlockingQueue 中有多个存储、提取以及检查的方法,有些方法虽然看起来重复,但是每个方法的作用是不一样的。
offer()
作用相同,不一样的是 add()
如果没有空间存储,也就是元素插入失败的时候会抛出异常 IllegalStateException。poll()
方法相同,不同的是如果队列为空,则会抛出异常队列类型 | 数据结构 | 是否阻塞 | 是否有界 | 特点 |
---|---|---|---|---|
ArrayBlockingQueue | 数组 | 是 | 是 | FIFO 规则排列 |
LinkedBlockingDeque | 链表 | 是 | 自定义(没有填写容量则是无界) | 双端队列 |
LinkedBlockingQueue | 链表 | 是 | 自定义(没有填写容量则是无界) | FIFO 规则排列 |
DelayQueue | 链表 | 是 | 否 | 可设置延迟时间 |
LinkedTransferQueue | 链表 | 是 | 否 | FIFO 规则排列,SynchronousQueue 的升级版 |
PriorityBlockingQueue | 数组 | 是 | 否 | 可设定排序规则 |
SynchronousQueue | 栈或队列 | 是 | 不存储元素 | 不存储元素 |
ArrayBlockingQueue 是一个用数组结构实现的有界阻塞队列,队列按照 FIFO 的规则进行元素的排列,也就是说队列的头结点是等待时间最长的节点,而尾节点则是等待时间最短的节点,新节点会插入到尾节点的后面。
有界的定义是,一旦队列初始化并且指定了大小以后,之后就再也不能改变队列的大小。
在 ArrayBlockingQueue 初始化的时候,还可以选择是否选择公平策略,公平策略主要决定的是队列中维护的 ReentrantLock 的策略。在 ReentrantLock 中知道,公平策略会决定是否是等待时间最长的线程出列,如果选择了公平策略,则为了维护这个策略会牺牲一定的效率。
public ArrayBlockingQueue(int capacity, boolean fair) {
if (capacity <= 0)
throw new IllegalArgumentException();
this.items = new Object[capacity];
lock = new ReentrantLock(fair);
notEmpty = lock.newCondition();
notFull = lock.newCondition();
}
ArrayBlockingQueue<Object> queue = new ArrayBlockingQueue<>(10);
queue.offer("1");
queue.offer("3");
queue.offer("2");
while (queue.peek() != null) {
System.out.println(queue.poll());
}
/**
* 入列
*/
private void enqueue(E x) {
// 插入元素
final Object[] items = this.items;
items[putIndex] = x;
// 指针指向第一个元素
if (++putIndex == items.length)
putIndex = 0;
count++;
notEmpty.signal();
}
/**
* 出列
*/
private E dequeue() {
// 元素出列
E x = (E) items[takeIndex];
items[takeIndex] = null;
// 指针指向第一个元素
if (++takeIndex == items.length)
takeIndex = 0;
count--;
// 删除迭代器队列中的元素
if (itrs != null)
itrs.elementDequeued();
notFull.signal();
return x;
}
DelayQueue 延迟队列是一个由链表结构组成的无界阻塞队列,只有在其中元素到延迟的时间时候才能取出,在实现的时候,使用了 PriorityQueue 作为元素的存储队列。
DelayQueue 中的元素必须实现 Delayed 接口,接口中有一个方法 getDelay()
,用来获取元素剩余的延迟时间,只有在延迟时间到了才能取出元素。在 ScheduledThreadPoolExecutor 中有该方法的实现例子。
public long getDelay(TimeUnit unit) {
return unit.convert(time - now(), NANOSECONDS);
}
DelayQueue 在使用的时候需要注意,因为 DelayQueue 中维护了一个优先级队列 PriorityQueue,DelayQueue 存储或者提取元素都是从 PriorityQueue 中提取。
而 PriorityQueue 存储元素的时候,会根据自定义实现的 compareTo()
方法来进行堆排序维护元素的顺序,所以在最后打印出来的元素顺序和存储顺序不一定相同。
public class Test {
static class Element implements Delayed {
private Object ele;
private final long createTime;
private long delay;
{
this.createTime = System.currentTimeMillis();
}
public Element(Object ele, long delay) {
this.ele = ele;
this.delay = delay;
}
@Override
public long getDelay(TimeUnit unit) {
return unit.convert(createTime + delay - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
}
@Override
public int compareTo(Delayed o) {
return (int) (this.getDelay(TimeUnit.MILLISECONDS) - o.getDelay(TimeUnit.MILLISECONDS));
}
@Override
public String toString() {
return String.format("Element: ele:[%s], delay:[%d]", ele, delay);
}
}
public static void main(String[] args) {
DelayQueue<Element> delayQueue = new DelayQueue<>();
delayQueue.offer(new Element("元素1", 1000));
delayQueue.offer(new Element("元素2", 4000));
delayQueue.offer(new Element("元素3", 3000));
delayQueue.offer(new Element("元素4", 2000));
while (delayQueue.size() != 0) {
try {
Element poll = delayQueue.take();
System.out.println(poll);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
/**
* 入列
*/
public boolean offer(E e) {