定义
支持阻塞的插入方法:意思是当队列满时,队列会阻塞插入元素的线程,直到队列不满。支持阻塞的移除方法:意思是在队列为空时,获取元素的线程会等待队列变为非空。
阻塞队列常用于生产者和消费者的场景,生产者是向队列里添加元素的线程,消费者是从队列里取元素的线程。阻塞队列就是生产者用来存放元素、消费者用来获取元素的容器。
ArrayBlockingQueue
由数组组成的无界阻塞队列。
一个建立在数组之上被BlockingQueue绑定的阻塞队列。这个队列元素顺序是先进先出。队列的头部是在队列中待的时间最长的元素。队列的尾部是再队列中待的时间最短的元素。新的元素会被插入到队列尾部,并且队列从队列头部获取元素。这是一个典型的绑定缓冲,在这个缓冲区中,有一个固定大小的数组持有生产者插入的数据,并且消费者会提取这些数据。一旦这个类被创建,那么这个数组的容量将不能再被改变。尝试使用put操作给一个满队列插入元素将导致这个操作被阻塞;尝试从空队列中取元素也会被阻塞。
这个类推荐了一个可选的公平策略来排序等待的生产者和消费者线程。默认的,这个顺序是不确定的。但是队列会使用公平的设置true来使线程按照先进先出顺序访问。通常公平性会减少吞吐量但是却减少了可变性以及避免了线程饥饿。
ArrayBlockingQueue fairQueue = new ArrayBlockingQueue(1000,true);
源码分析
/**
*capacity指定队列的大小
*fair指定是否公平
*/
public ArrayBlockingQueue(int capacity, boolean fair) {
if (capacity <= 0)
throw new IllegalArgumentException();
this.items = new Object[capacity];
lock = new ReentrantLock(fair);
// 这两个notEmpty和notFull参数实际上是Condition,而Condition可以把它看做一个阻塞信号
// Condition的子类ConditionObject(是AbstractQueuedSynchronizer的内部类)拥有两个方法signal和
// signalAll方法,前一个方法是唤醒队列中得第一个线程,而signalAll是唤醒队列中得所有等待线程,
// 但是只有一个等待的线程会被选择,这两个方法可以看做notify和notifyAll的变体。
notEmpty = lock.newCondition();
notFull = lock.newCondition();
}
在这个阻塞队列的insert和remove方法中都会被调用signal来唤醒等待线程,在put方法中,如果队列已经满了,则会调用await方法来,直到队列有空位,才会调用insert方法插入元素。源代码如下:
public void put(E e) throws InterruptedException {
checkNotNull(e);
final ReentrantLock lock = this.lock;
// 线程可以被打断
lock.lockInterruptibly();
try {
// 如果队列满进行等待
while (count == items.length)
notFull.await();
insert(e);
} finally {
lock.unlock();
}
}
private void insert(E x) {
items[putIndex] = x;
putIndex = inc(putIndex);
++count;
notEmpty.signal();
}
如果不想在队列满了之后,再插入元素被阻塞,提供了offer方法,这个offer方法有重载方法,调用offer(E e)方法时,如果队列已经满了,那么会直接返回一个false,如果没有满,则直接调用insert插入到队列中;调用offer(E e, long timeout, TimeUnit unit)方法时,会在队列满了之后阻塞队列,但是这里可以由开发人员设置超时时间,如果超时时队列还是满的,则会以false返回。源码如下所示
public boolean offer(E e, long timeout, TimeUnit unit)
throws InterruptedException {
checkNotNull(e);
long nanos = unit.toNanos(timeout);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == items.length) {
if (nanos <= 0)
return false;
nanos = notFull.awaitNanos(nanos);
}
insert(e);
return true;
} finally {
lock.unlock();
}
}
public boolean offer(E e) {
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lock();
try {
if (count == items.length)
return false;
else {
insert(e);
return true;
}
} finally {
lock.unlock();
}
}
插入数据有阻塞和非阻塞之分,那么提取数据也肯定就有阻塞与非阻塞之分了。
其中take方法是个阻塞方法,当队列为空时,就被阻塞,源码如下:
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0)
notEmpty.await();
return extract();
} finally {
lock.unlock();
}
}
方法poll是重载方法,跟offer相对,也有基础方法和超时方法之分。
在这个类中还提供了peek方法来提取数据,但是peek方法是从对了的tail提取,而pool是从队列的head提取,即peek提取的元素是进入队列最晚的,而pool提取的元素是进入队列最早时间最长的元素。下面看看pool的源码
public E poll() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return (count == 0) ? null : extract();
} finally {
lock.unlock();
}
}
public E poll(long timeout, TimeUnit unit) throws InterruptedException {
long nanos = unit.toNanos(timeout);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0) {
if (nanos <= 0)
return null;
nanos = notEmpty.awaitNanos(nanos);
}
return extract();
} finally {
lock.unlock();
}
}
再来看看这个类中得迭代器。这个类中的迭代器是线程安全的,因为在实现的next和remove方法中都加了lock了
private class Itr implements Iterator<E> {
private int remaining; // Number of elements yet to be returned
private int nextIndex; // Index of element to be returned by next
private E nextItem; // Element to be returned by next call to next
private E lastItem; // Element returned by last call to next
private int lastRet; // Index of last element returned, or -1 if none
Itr() {
final ReentrantLock lock = ArrayBlockingQueue.this.lock;
lock.lock();
try {
lastRet = -1;
if ((remaining = count) > 0)
nextItem = itemAt(nextIndex = takeIndex);
} finally {
lock.unlock();
}
}
public boolean hasNext() {
return remaining > 0;
}
public E next() {
final ReentrantLock lock = ArrayBlockingQueue.this.lock;
lock.lock(); //锁定
try {
if (remaining <= 0)
throw new NoSuchElementException();
lastRet = nextIndex;
E x = itemAt(nextIndex); // check for fresher value
if (x == null) {
x = nextItem; // we are forced to report old value
lastItem = null; // but ensure remove fails
}
else
lastItem = x;
while (--remaining > 0 && // skip over nulls
(nextItem = itemAt(nextIndex = inc(nextIndex))) == null)
;
return x;
} finally {
lock.unlock();
}
}
public void remove() {
final ReentrantLock lock = ArrayBlockingQueue.this.lock;
lock.lock();
try {
int i = lastRet;
if (i == -1)
throw new IllegalStateException();
lastRet = -1;
E x = lastItem;
lastItem = null;
// only remove if item still at index
if (x != null && x == items[i]) {
boolean removingHead = (i == takeIndex);
removeAt(i);
if (!removingHead)
nextIndex = dec(nextIndex);
}
} finally {
lock.unlock();
}
}
}
总结
1.一旦创建,则容量不能再改动
2.这个类是线程安全的,并且迭代器也是线程安全的
3.这个类的put和take方法分别会在队列满了和队列空了之后被阻塞操作。
4.这个类提供了offer和poll方法来插入和提取元素,而不会在队列满了或者队列为空时阻塞操作。
5.这个队列的锁默认是不公平策略,即唤醒线程的顺序是不确定的。
LinkedBlockingQueue
一个由链表结构组成的有界阻塞队列。链表是单向链表,而不是双向链表
ArrayBlockingQueue只有1个锁,添加数据和删除数据的时候只能有1个被执行,不允许并行执行。而LinkedBlockingQueue有2个锁,放锁和拿锁,添加数据和删除数据是可以并行进行的,当然添加数据和删除数据的时候只能有1个线程各自执行。
LinkedBlockingQueue入队方法:add、offer、put方法。
public boolean add(E e) {
if (offer(e))
return true;
else
throw new IllegalStateException("Queue full");
}
public boolean offer(E e) {
if (e == null) throw new NullPointerException(); // 不允许空元素
final AtomicInteger count = this.count;
if (count.get() == capacity) // 如果容量满了,返回false
return false;
int c = -1;
Node<E> node = new Node(e); // 容量没满,以新元素构造节点
final ReentrantLock putLock = this.putLock;
putLock.lock(); // 放锁加锁,保证调用offer方法的时候只有1个线程
try {
if (count.get() < capacity) { // 再次判断容量是否已满,因为可能拿锁在进行消费数据,没满的话继续执行
enqueue(node); // 节点添加到链表尾部
c = count.getAndIncrement(); // 元素个数+1
if (c + 1 < capacity) // 如果容量还没满
notFull.signal(); // 在放锁的条件对象notFull上唤醒正在等待的线程,表示可以再次往队列里面加数据了,队列还没满
}
} finally {
putLock.unlock(); // 释放放锁,让其他线程可以调用offer方法
}
if (c == 0) // 由于存在放锁和拿锁,这里可能拿锁一直在消费数据,count会变化。这里的if条件表示如果队列中还有1条数据
signalNotEmpty(); // 在拿锁的条件对象notEmpty上唤醒正在等待的1个线程,表示队列里还有1条数据,可以进行消费
return c >= 0; // 添加成功返回true,否则返回false
}
public void put(E e) throws InterruptedException {
if (e == null) throw new NullPointerException(); // 不允许空元素
int c = -1;
Node<E> node = new Node(e); // 以新元素构造节点
final ReentrantLock putLock = this.putLock;
final AtomicInteger count = this.count;
putLock.lockInterruptibly(); // 放锁加锁,保证调用put方法的时候只有1个线程
try {
while (count.get() == capacity) { // 如果容量满了
notFull.await(); // 阻塞并挂起当前线程
}
enqueue(node); // 节点添加到链表尾部
c = count.getAndIncrement(); // 元素个数+1
if (c + 1 < capacity) // 如果容量还没满
notFull.signal(); // 在放锁的条件对象notFull上唤醒正在等待的线程,表示可以再次往队列里面加数据了,队列还没满
} finally {
putLock.unlock(); // 释放放锁,让其他线程可以调用put方法
}
if (c == 0) // 由于存在放锁和拿锁,这里可能拿锁一直在消费数据,count会变化。这里的if条件表示如果队列中还有1条数据
signalNotEmpty(); // 在拿锁的条件对象notEmpty上唤醒正在等待的1个线程,表示队列里还有1条数据,可以进行消费
}
ArrayBlockingQueue中放入数据阻塞的时候,需要消费数据才能唤醒。而LinkedBlockingQueue中放入数据阻塞的时候,因为它内部有2个锁,可以并行执行放入数据和消费数据,不仅在消费数据的时候进行唤醒插入阻塞的线程,同时在插入的时候如果容量还没满,也会唤醒插入阻塞的线程。
LinkedBlockingQueue出队方法:poll、take、remove方法
public E poll() {
final AtomicInteger count = this.count;
if (count.get() == 0) // 如果元素个数为0
return null; // 返回null
E x = null;
int c = -1;
final ReentrantLock takeLock = this.takeLock;
takeLock.lock(); // 拿锁加锁,保证调用poll方法的时候只有1个线程
try {
if (count.get() > 0) { // 判断队列里是否还有数据
x = dequeue(); // 删除头结点
c = count.getAndDecrement(); // 元素个数-1
if (c > 1) // 如果队列里还有元素
notEmpty.signal(); // 在拿锁的条件对象notEmpty上唤醒正在等待的线程,表示队列里还有数据,可以再次消费
}
} finally {
takeLock.unlock(); // 释放拿锁,让其他线程可以调用poll方法
}
if (c == capacity) // 由于存在放锁和拿锁,这里可能放锁一直在添加数据,count会变化。这里的if条件表示如果队列中还可以再插入数据
signalNotFull(); // 在放锁的条件对象notFull上唤醒正在等待的1个线程,表示队列里还能再次添加数据
return x;
public E take() throws InterruptedException {
E x;
int c = -1;
final AtomicInteger count = this.count;
final ReentrantLock takeLock = this.takeLock;
takeLock.lockInterruptibly(); // 拿锁加锁,保证调用take方法的时候只有1个线程
try {
while (count.get() == 0) { // 如果队列里已经没有元素了
notEmpty.await(); // 阻塞并挂起当前线程
}
x = dequeue(); // 删除头结点
c = count.getAndDecrement(); // 元素个数-1
if (c > 1) // 如果队列里还有元素
notEmpty.signal(); // 在拿锁的条件对象notEmpty上唤醒正在等待的线程,表示队列里还有数据,可以再次消费
} finally {
takeLock.unlock(); // 释放拿锁,让其他线程可以调用take方法
}
if (c == capacity) // 由于存在放锁和拿锁,这里可能放锁一直在添加数据,count会变化。这里的if条件表示如果队列中还可以再插入数据
signalNotFull(); // 在放锁的条件对象notFull上唤醒正在等待的1个线程,表示队列里还能再次添加数据
return x;
}
public boolean remove(Object o) {
if (o == null) return false;
fullyLock(); // remove操作要移动的位置不固定,2个锁都需要加锁
try {
for (Node<E> trail = head, p = trail.next; // 从链表头结点开始遍历
p != null;
trail = p, p = p.next) {
if (o.equals(p.item)) { // 判断是否找到对象
unlink(p, trail); // 修改节点的链接信息,同时调用notFull的signal方法
return true;
}
}
return false;
} finally {
fullyUnlock(); // 2个锁解锁
}
}
LinkedBlockingQueue的take方法对于没数据的情况下会阻塞,poll方法删除链表头结点,remove方法删除指定的对象。需要注意的是remove方法由于要删除的数据的位置不确定,需要2个锁同时加锁
PriorityBlockingQueue
一个支持优先级排序的无界阻塞队列。
PriorityBlockingQueue通过使用堆这种数据结构实现将队列中的元素按照某种排序规则进行排序,从而改变先进先出的队列顺序,提供开发者改变队列中元素的顺序的能力。队列中的元素必须是可比较的,即实现Comparable接口,或者在构建函数时提供可对队列元素进行比较的Comparator对象。堆是一种二叉树结构,堆的根元素是整个树的最大值或者最小值(称为大顶堆或者小顶堆),同时堆的每个子树都是满足堆的树结构。由于堆的顶部是最大值或者最小值,所以每次从堆获取数据都是直接获取堆顶元素,然后再将堆调整成堆结构。
入队方法:
此处虽然PriorityBlockingQueue是阻塞队列,但是其并没有阻塞的入队方法,因为该队列是无界的,所以入队是不会阻塞的。
public boolean offer(E e) {
final ReentrantLock lock = this.lock;
lock.lock();// 加锁
try {
// 通过PriorityQueue入队一个元素
boolean ok = q.offer(e);
assert ok;
// 唤醒等在notEmpty上的线程
notEmpty.signal();
return true;
} finally {
lock.unlock();
}
}
offer()方法正如在结构介绍中提到的通过组合的方式,通过外部加锁内部直接调用PriorityQueue的offer()方法。所以主要的工作在PriorityQueue内部
public boolean offer(E e) {
if (e == null)
throw new NullPointerException();
modCount++;
int i = size;
// 内部使用数组保存队列的元素,所以如果队列的大小超过数组的长度,则需要进行扩容
// 扩容的标准是:<64扩大2倍,>=64则扩大1.5倍
if (i >= queue.length)
grow(i + 1);
size = i + 1;
// i==0表示队列目前没有元素,则直接将带插入元素添加到数组即可
if (i == 0)
queue[0] = e;
else
// 将带插入元素添加到队列的最后一个元素,然后自下而上调整堆
siftUp(i, e);
return true;
}
private void siftUp(int k, E x) {
// 两者逻辑一样,只是采用的比较方式不同而已
if (comparator != null)
siftUpUsingComparator(k, x);
else
siftUpComparable(k, x);
}
private void siftUpUsingComparator(int k, E x) {
// 循环,直到根元素
while (k > 0) {
// 寻找k的父元素下标,固定规则
int parent = (k - 1) >>> 1;
Object e = queue[parent];
// 如果x >= e,即子节点>=父节点,则直接退出循环
// 解释:自下而上一般出现在插入元素时调用,插入元素是插入到队列的最后,则需要将该元素调整到合适的位置
// 即从队列的最后往上调整堆,直到不小于其父节点为止,相当于冒泡
if (comparator.compare(x, (E) e) >= 0)
break;
// 如果当前节点<其父节点,则将其与父节点进行交换,并继续往上访问父节点
queue[k] = e;
k = parent;
}
queue[k] = x;
}
入队时通过调用ReentrantLock.lock()进行加锁,然后调用PriorityQueue.offer()方法进行入队操作,最后通过Condition.signal()唤醒等待其上的线程。PriorityQueue.offer()方法将元素插入到队列的最后,然后自上而下调整堆
出队方法
poll(long, TimeUnit)是poll()的阻塞版本,同时take()是无限阻塞版poll()(即无期限阻塞,直到获取到数据),通过Condition.awaitNanos()实现阻塞
public E poll(long timeout, TimeUnit unit) throws InterruptedException {
long nanos = unit.toNanos(timeout);
final ReentrantLock lock = this.lock;
// 此处不同于其他非阻塞方法,调用了ReentrantLock的lockInterruptibly()方法,考虑了当前线程是否被打断
lock.lockInterruptibly();
try {
// 循环,直到获取到元素,或者到达等待时间
for (;;) {
// 从PriorityQueue获取一个元素,该方法不会阻塞
E x = q.poll();
if (x != null)
return x;
// 此处的nanos会因为每次调用Condition.awaitNanos而减少,如果<0则说明累计等待时间已达到设定的等待时间
if (nanos <= 0)
return null;
try {
// Condition.awaitNanos指定等待时间,但是有可能会被“虚假唤醒”(参考API),导致等待时间未满,返回值即剩余的等待时间
// 所以需要在外层进行循环,每次等待的时候是上次剩余的时间
nanos = notEmpty.awaitNanos(nanos);
} catch (InterruptedException ie) {
notEmpty.signal(); // propagate to non-interrupted thread
throw ie;
}
}
} finally {
lock.unlock();
}
}
PriorityQueue.poll()
public E poll() {
// size==0队列为0,直接返回null
if (size == 0)
return null;
int s = --size;
modCount++;
// 出队总是将数组的第一个元素进行出队,
E result = (E) queue[0];
E x = (E) queue[s];
queue[s] = null;
if (s != 0)
// 同时将队列的最后一个元素放到第一个位置,然后自上而下调整堆
siftDown(0, x);
return result;
}
private void siftDown(int k, E x) {
if (comparator != null)
siftDownUsingComparator(k, x);
else
siftDownComparable(k, x);
}
private void siftDownUsingComparator(int k, E x) {
// 由于堆是一个二叉树,所以size/2是树中的最后一个非叶子节点
// 如果k是叶子节点,那么其无子节点,则不需要再往下调整堆
int half = size >>> 1;
while (k < half) {
int child = (k << 1) + 1;
Object c = queue[child];
// 右节点
int right = child + 1;
// 找出两个子节点以及父节点中较小的一个
if (right < size &&
comparator.compare((E) c, (E) queue[right]) > 0)
c = queue[child = right];
// 如果父节点最小,则无需继续往下调整堆
if (comparator.compare(x, (E) c) <= 0)
break;
// 否则将父节点与两个子节点中较小的一个交换,然后往下继续调整
queue[k] = c;
k = child;
}
queue[k] = x;
}
public boolean remove(Object o) {
// 在队列中查询元素,返回待删除元素在队列中的位置
int i = indexOf(o);
if (i == -1)
return false;
else {
// 删除指定位置的元素
removeAt(i);
return true;
}
}
private E removeAt(int i) {
assert i >= 0 && i < size;
modCount++;
int s = --size;
if (s == i) // removed last element
queue[i] = null;
else {
// 删除最后一个元素,将最后一个元素放到i的位置,然后从i开始上而下调整堆
E moved = (E) queue[s];
queue[s] = null;
siftDown(i, moved);
// 如果queue[i] == moved说明未发生调整,那么则需要自下而上调整堆
if (queue[i] == moved) {
siftUp(i, moved);
if (queue[i] != moved)
return moved;
}
}
return null;
}
当删除堆中的一个元素时,将堆的最后一个元素移动到被删除的位置,然后将最后一个位置值为NULL,当把最后一个元素移动到堆中的某个位置时,这时首先需要从该位置开始自上而下的调整堆,如果该位置的元素在调整时发生变化,即堆有变化,则说明该元素是大于其子节点的,那么该节点就不可能小于其上的父节点(因为堆的结构是传递性的,即子节点小于父节点,其孙子节点同时小于其父节点),所以就不需要再向上调整了;但是如果未发生变化,则说明该位置的节点小于其子节点,那么就无法保证其一定比父节点大,所以需要从该节点开始自上而下的调整堆
DelayQueue
一个使用优先级队列实现的无界阻塞队列。
DelayQueue是一个支持延时获取元素的无界阻塞队列。队列使用PriorityQueue来实现。队列中的元素必须实现Delayed接口,在创建元素时可以指定多久才能从队列中获取当前元素。只有在延迟期满时才能从队列中提取元素。
DelayQueue非常有用,可以将DelayQueue运用在以下应用场景。
缓存系统的设计:可以用DelayQueue保存缓存元素的有效期,使用一个线程循环查询DelayQueue,一旦能从DelayQueue中获取元素时,表示缓存有效期到了。
定时任务调度:使用DelayQueue保存当天将会执行的任务和执行时间,一旦从DelayQueue中获取到任务就开始执行,比如TimerQueue就是使用DelayQueue实现的。
1、如何实现delayed接口
DelayQueue队列的元素必须实现Delayed接口。我们可以参考ScheduledThreadPoolExecutor里ScheduledFutureTask类的实现,一共有三步。
第一步:在对象创建的时候,初始化基本数据。使用time记录当前对象延迟到什么时候可以使用,使用sequenceNumber来标识元素在队列中的先后顺序。代码如下。
private static final AtomicLong sequencer = new AtomicLong(0);
ScheduledFutureTask(Runnable r, V result, long ns, long period) {
super(r, result);
this.time = ns;
this.period = period;
this.sequenceNumber = sequencer.getAndIncrement();
}
第二步:实现getDelay方法,该方法返回当前元素还需要延时多长时间,单位是纳秒,代码如下
public long getDelay(TimeUnit unit) {
return unit.convert(time - now(), TimeUnit.NANOSECONDS);
}
通过构造函数可以看出延迟时间参数ns的单位是纳秒,自己设计的时候最好使用纳秒,因为实现getDelay()方法时可以指定任意单位,一旦以秒或分作为单位,而延时时间又精确不到纳秒就麻烦了。使用时请注意当time小于当前时间时,getDelay会返回负数
第三步:实现compareTo方法来指定元素的顺序。例如,让延时时间最长的放在队列的末尾。实现代码如下。
public int compareTo(Delayed other) {
if (other == this) // compare zero ONLY if same object
return 0;
if (other instanceof ScheduledFutureTask) {
ScheduledFutureTask<> x = (ScheduledFutureTask<>)other;
long diff = time - x.time;
if (diff < 0)
return -1;
else if (diff > 0)
return 1;
else if (sequenceNumber < x.sequenceNumber)
return -1;
else
return 1;
}
long d = (getDelay(TimeUnit.NANOSECONDS) -
other.getDelay(TimeUnit.NANOSECONDS));
return (d == 0) 0 : ((d < 0) -1 : 1);
}
2、如何实现延时阻塞队列
元素没有达到延时时间,就阻塞当前线程
public E poll(long timeout, TimeUnit unit) throws InterruptedException {
long nanos = unit.toNanos(timeout);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
for (;;) {
E first = q.peek();
if (first == null) {
if (nanos <= 0)
return null;
else
nanos = available.awaitNanos(nanos);
} else {
long delay = first.getDelay(TimeUnit.NANOSECONDS);
if (delay <= 0)
return q.poll();
if (nanos <= 0)
return null;
if (nanos < delay || leader != null)
nanos = available.awaitNanos(nanos);
else {
Thread thisThread = Thread.currentThread();
leader = thisThread;
try {
long timeLeft = available.awaitNanos(delay);
nanos -= delay - timeLeft;
} finally {
if (leader == thisThread)
leader = null;
}
}
}
}
} finally {
if (leader == null && q.peek() != null)
available.signal();
lock.unlock();
}
}
代码中的变量leader是一个等待获取队列头部元素的线程。如果leader不等于空,表示已经有线程在等待获取队列的头元素。所以,使用await()方法让当前线程等待信号。如果leader等于空,则把当前线程设置成leader,并使用awaitNanos()方法让当前线程等待接收信号或等待delay时间
SynchronousQueue
一个不存储元素的阻塞队列。
SynchronousQueue是一个不存储元素的阻塞队列。每一个put操作必须等待一个take操作,否则不能继续添加元素。它支持公平访问队列。默认情况下线程采用非公平性策略访问队列。使用以下构造方法可以创建公平性访问的SynchronousQueue,如果设置为true,则等待的线程会采用先进先出的
顺序访问队列。
public SynchronousQueue(boolean fair) {
transferer = fair new TransferQueue() : new TransferStack();
}
SynchronousQueue可以看成是一个传球手,负责把生产者线程处理的数据直接传递给消费者线程。队列本身并不存储任何元素,非常适合传递性场景。SynchronousQueue的吞吐量高于LinkedBlockingQueue和ArrayBlockingQueue,感觉使用的场景不多,这里就不详细介绍了。
LinkedTransferQueue
一个由链表结构组成的无界阻塞队列。
LinkedTransferQueue是一个由链表结构组成的无界阻塞TransferQueue队列。相对于其他阻
塞队列,LinkedTransferQueue多了tryTransfer和transfer方法。
(1)transfer方法
如果当前有消费者正在等待接收元素(消费者使用take()方法或带时间限制的poll()方法时),transfer方法可以把生产者传入的元素立刻transfer(传输)给消费者。如果没有消费者在等待接收元素,transfer方法会将元素存放在队列的tail节点,并等到该元素被消费者消费了才返回。transfer方法的关键代码如下。
Node pred = tryAppend(s, haveData);
return awaitMatch(s, pred, e, (how == TIMED), nanos);
第一行代码是试图把存放当前元素的s节点作为tail节点。第二行代码是让CPU自旋等待消费者消费元素。因为自旋会消耗CPU,所以自旋一定的次数后使用Thread.yield()方法来暂停当前正在执行的线程,并执行其他线程。
(2)tryTransfer方法
tryTransfer方法是用来试探生产者传入的元素是否能直接传给消费者。如果没有消费者等待接收元素,则返回false。和transfer方法的区别是tryTransfer方法无论消费者是否接收,方法立即返回,而transfer方法是必须等到消费者消费了才返回。对于带有时间限制的tryTransfer(E e,long timeout,TimeUnit unit)方法,试图把生产者传入的元素直接传给消费者,但是如果没有消费者消费该元素则等待指定的时间再返回,如果超时还没消费元素,则返回false,如果在超时时间内消费了元素,则返回true。
LinkedBlockingDeque
一个由链表结构组成的双向阻塞队列。LinkedBlockingDeque是一个由链表结构组成的双向阻塞队列。所谓双向队列指的是可以从队列的两端插入和移出元素。双向队列因为多了一个操作队列的入口,在多线程同时入队时,也就减少了一半的竞争。相比其他的阻塞队列,LinkedBlockingDeque多了addFirst、
addLast、offerFirst、offerLast、peekFirst和peekLast等方法,以First单词结尾的方法,表示插入、获取(peek)或移除双端队列的第一个元素。以Last单词结尾的方法,表示插入、获取或移除双端队列的最后一个元素。另外,插入方法add等同于addLast,移除方法remove等效于removeFirst。但是take方法却等同于takeFirst,不知道是不是JDK的bug,使用时还是用带有First和Last后缀的方法更清楚。