概述
阻塞队列:用于保存等待执行的任务的阻塞队列。下面是常用的几个阻塞队列。
- ArrayBlockingQueue:是基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素进行排序。
- LinkedBlockingQueue:是基于链表结构的阻塞队列,此队列按FIFO (先进先出) 排序元素,吞吐量通常要高于ArrayBlockingQueue。静态工厂方法Executors.newFixedThreadPool()使用了这个队列。
- PriorityBlockingQueue:是具有优先级的无限阻塞队列。
- SynchronousQueue:是不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQueue,静态工厂方法Executors.newCachedThreadPool使用了这个队列。
源码解析
前置知识点: ReentrantLock + Condition
1. ArrayBlockingQueue
ArrayBlockingQueue是一个基于数组结构实现的FIFO阻塞队列,在构造该阻塞队列时需要指定队列的容量。当队列已满时,若再次进行数据写入操作,则线程将会进入阻塞,一直等待直到其他线程对元素进行消费。当队列为空时,对该队列的消费线程将会进入阻塞,直到有其他线程写入数据。
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();
}
入队
public void put(E e) throws InterruptedException {
Objects.requireNonNull(e);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
// 队列已满 则notFull条件等待
while (count == items.length)
notFull.await();
// 入队
enqueue(e);
} finally {
lock.unlock();
}
}
出队
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
// 队列为空 notEmpty条件等待
while (count == 0)
notEmpty.await();
// 出队
return dequeue();
} finally {
lock.unlock();
}
}
private E dequeue() {
// assert lock.isHeldByCurrentThread();
// assert lock.getHoldCount() == 1;
// assert items[takeIndex] != null;
final Object[] items = this.items;
@SuppressWarnings("unchecked")
E e = (E) items[takeIndex];
items[takeIndex] = null;
if (++takeIndex == items.length) takeIndex = 0;
count--;
if (itrs != null)
itrs.elementDequeued();
// 队列不满 notFull条件唤醒
notFull.signal();
return e;
}
用个简单的示意图描述同步机制。
2. LinkedBlockingQueue
LinkedBlockingQueue是基于单向链表实现的阻塞队列。默认无界队列(Integer.MAX_VALUE),也可以指定容量。
// 无界队列
public LinkedBlockingQueue() {
this(Integer.MAX_VALUE);
}
public LinkedBlockingQueue(int capacity) {
if (capacity <= 0) throw new IllegalArgumentException();
this.capacity = capacity;
last = head = new Node<E>(null);
}
static class Node<E> {
E item;
/**
* One of:
* - the real successor Node
* - this Node, meaning the successor is head.next
* - null, meaning there is no successor (this is the last node)
*/
Node<E> next;
Node(E x) { item = x; }
}
/** 队列容量 */
private final int capacity;
/** 队列中元素数量 */
private final AtomicInteger count = new AtomicInteger();
/** 队头 */
transient Node<E> head;
/** 队尾 */
private transient Node<E> last;
/** take, poll等读操作需要持有这个锁 */
private final ReentrantLock takeLock = new ReentrantLock();
/** 读操作队列不空条件 */
private final Condition notEmpty = takeLock.newCondition();
/** put, offer等写操作需要持有这个锁*/
private final ReentrantLock putLock = new ReentrantLock();
/** 写操作队列不满条件 */
private final Condition notFull = putLock.newCondition();
入队
public void put(E e) throws InterruptedException {
if (e == null) throw new NullPointerException();
final int c;
final Node<E> node = new Node<E>(e);
final ReentrantLock putLock = this.putLock;
final AtomicInteger count = this.count;
putLock.lockInterruptibly();
try {
/*
* Note that count is used in wait guard even though it is
* not protected by lock. This works because count can
* only decrease at this point (all other puts are shut
* out by lock), and we (or some other waiting put) are
* signalled if it ever changes from capacity. Similarly
* for all other uses of count in other wait guards.
*/
// 队列已满 notFull条件等待
while (count.get() == capacity) {
notFull.await();
}
enqueue(node);
c = count.getAndIncrement();
// 队列不满 notFull条件唤醒
if (c + 1 < capacity)
notFull.signal();
} finally {
putLock.unlock();
}
// 原队列为空, 则notEmpty条件唤醒 (c为原始值 非入队+1后)
if (c == 0)
signalNotEmpty();
}
出队
public E take() throws InterruptedException {
final E x;
final int c;
final AtomicInteger count = this.count;
final ReentrantLock takeLock = this.takeLock;
takeLock.lockInterruptibly();
try {
// 队列为空 notEmpty条件等待
while (count.get() == 0) {
notEmpty.await();
}
// 出队
x = dequeue();
c = count.getAndDecrement();
// 队列不空 notEmpty条件唤醒
if (c > 1)
notEmpty.signal();
} finally {
takeLock.unlock();
}
// 原队列已满, 则notFull条件唤醒 (c为原始值 非出队-1后)
if (c == capacity)
signalNotFull();
return x;
}
用个简单的示意图描述同步机制。
3. PriorityBlockingQueue
PriorityBlockingQueue优先级阻塞队列是一个“无边界”阻塞队列,底层是由堆实现,会根据规则器(Comparator)对插入队列尾部的元素进行排序。当添加元素时,会根据元素的优先级自动排序,获取元素时会返回当前队列中优先级最高的元素。当队列为空时,获取元素的操作将会阻塞,直到队列中有元素可用。
private transient Object[] queue;
private transient Comparator<? super E> comparator;
private final ReentrantLock lock = new ReentrantLock();
// 只有一个队列不空的条件
private final Condition notEmpty = lock.newCondition();
public PriorityBlockingQueue(int initialCapacity,
Comparator<? super E> comparator) {
if (initialCapacity < 1)
throw new IllegalArgumentException();
this.comparator = comparator;
this.queue = new Object[Math.max(1, initialCapacity)];
}
入队
public boolean offer(E e) {
if (e == null)
throw new NullPointerException();
final ReentrantLock lock = this.lock;
lock.lock();
int n, cap;
Object[] es;
//队列已满则扩容
while ((n = size) >= (cap = (es = queue).length))
tryGrow(es, cap);
try {
// 通过比较器设置值
final Comparator<? super E> cmp;
if ((cmp = comparator) == null)
siftUpComparable(n, e, es);
else
siftUpUsingComparator(n, e, es, cmp);
size = n + 1;
// 队列不空 notEmpty条件唤醒
notEmpty.signal();
} finally {
lock.unlock();
}
return true;
}
出队
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
E result;
try {
// 出队 队列为空 notEmpty条件等待
while ( (result = dequeue()) == null)
notEmpty.await();
} finally {
lock.unlock();
}
return result;
}
用个简单的示意图描述同步机制。
4. SynchronousQueue
SynchronousQueue是一个特殊的阻塞队列,内部没有存储空间,它并不保存任何元素,也就不能peek操作。所有入队的线程都会被阻塞。 每次Put操作必须等待另一个线程的take操作,take操作必须等待put操作,因此它可以用于两个线程之间进行数据交换。
SynchronousQueue内部分公平与非公平两种数据结构。
- 公平:TransferQueue
- 非公平:TransferStack
// 默认非公平
public SynchronousQueue() {
this(false);
}
public SynchronousQueue(boolean fair) {
transferer = fair ? new TransferQueue() : new TransferStack();
}
下面以公平模式TransferQueue为例,研究下它的实现原理。
可以看到put和take方法都是用了transfer方法, 不同的点在于put方法第一个参数是数据, 而take是null。后面也是根据此来进行模式匹配。
public void put(E o) throws InterruptedException {
if (o == null) throw new NullPointerException();
if (transferer.transfer(o, false, 0) == null) {
Thread.interrupted();
throw new InterruptedException();
}
}
public E take() throws InterruptedException {
Object e = transferer.transfer(null, false, 0);
if (e != null)
return (E)e;
Thread.interrupted();
throw new InterruptedException();
}
核心方法transfer
Object transfer(Object e, boolean timed, long nanos) {
/* 基本算法是循环尝试采取以下两种操作:
*
* 1. 若队列为空或是相同模式,尝试将节点添加到等待队列中,等待完成(或取消)并返回匹配项。
* 2. 若队列不为空且是互补模式,尝试用CAS来设置等待节点的item字段并将其出队,然后返回匹配的item。
*/
QNode s = null; // constructed/reused as needed
boolean isData = (e != null);
for (;;) {
QNode t = tail;
QNode h = head;
if (t == null || h == null) // saw uninitialized value
continue; // spin
// 第一种情况,队列为空或是相同模式
if (h == t || t.isData == isData) { // empty or same-mode
QNode tn = t.next;
if (t != tail) // inconsistent read
continue;
if (tn != null) { // lagging tail
// 如果尾节点不为空, 那么修改尾节点,继续自旋
advanceTail(t, tn);
continue;
}
if (timed && nanos <= 0) // can't wait
// 已超时,直接返回null
return null;
if (s == null)
s = new QNode(e, isData);
// cas设置尾节点后继节点为s
if (!t.casNext(null, s)) // failed to link in
continue;
// 设置当前节点s为尾节点
advanceTail(t, s); // swing tail and wait
// 等待条件满足 唤醒
Object x = awaitFulfill(s, e, timed, nanos);
// 执行到这里 已被唤醒
// 节点被取消
if (x == s) { // wait was cancelled
// 清除取消节点
clean(t, s);
return null;
}
// 如果s没有自连接(还在队列中) 需要出队,这边s已经被取出。
if (!s.isOffList()) { // not already unlinked
advanceHead(t, s); // unlink if head
if (x != null) // and forget fields
s.item = s;
s.waiter = null;
}
return (x != null) ? x : e;
} else { // complementary-mode
// 第二钟情况, 队列不为空且是互补模式
QNode m = h.next; // node to fulfill
if (t != tail || m == null || h != head)
continue; // inconsistent read
Object x = m.item;
if (isData == (x != null) || // m already fulfilled
x == m || // m cancelled
!m.casItem(x, e)) { // lost CAS
advanceHead(h, m); // dequeue and retry
continue;
}
// 成功匹配, 设置头结点。 移除掉前一个head
advanceHead(h, m); // successfully fulfilled
// 唤醒线程
LockSupport.unpark(m.waiter);
return (x != null) ? x : e;
}
}
}
// 自旋或阻塞,直到满足条件
Object awaitFulfill(QNode s, Object e, boolean timed, long nanos) {
/* Same idea as TransferStack.awaitFulfill */
long lastTime = timed ? System.nanoTime() : 0;
Thread w = Thread.currentThread();
// 需要自旋的次数
int spins = ((head.next == s) ?
(timed ? maxTimedSpins : maxUntimedSpins) : 0);
for (;;) {
// 线程中断,取消这个节点
if (w.isInterrupted())
// s.item = this
s.tryCancel(e);
Object x = s.item;
// 返回
if (x != e)
return x;
// 有超时时间
if (timed) {
long now = System.nanoTime();
nanos -= now - lastTime;
lastTime = now;
if (nanos <= 0) {
s.tryCancel(e);
continue;
}
}
// 自旋
if (spins > 0)
--spins;
// 如果自旋达到了最大的次数,那么检测
else if (s.waiter == null)
s.waiter = w;
// 自旋到了最大的次数且没有设置超时时间,线程挂起
else if (!timed)
LockSupport.park(this);
// 剩余时间大于这个阈值挂起,否则就不要进行挂起了,自旋的性能会比较好
else if (nanos > spinForTimeoutThreshold)
LockSupport.parkNanos(this, nanos);
}
}
代码可以看出是 队尾匹配, 队头出队。其中队头出队可以保证FIFO。 而为什么可以由队尾匹配, 是因为如果队尾与前面节点模式必然相同,否则前面的节点都已经会被取出了。
【本文完】