什么是LinkedBlockingQueue?
LinkedBlockingQueue是JUC中基于独占锁实现的阻塞队列。它本身底层是由链表实现的,内部使用ReentrantLock来作为锁。
不同于ConcurrentLinkedQueue,LinkedBlockingQueue在队列满时,put()方法会阻塞,队列为空的时候,take()方法会阻塞。
一般使用LinkedBlockingQueue来实现生产者-消费者模型,来解耦线程,也可以作为线程池的工作队列。
LinkedBlockingQueue的内部结构
内部节点
java
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; }
}
这是一个典型的单向链表节点。 其中的next有三种情况:
- 当该节点仍在队列中时,
next指向下一个有效节点 next指向自身,代表该节点已经出队。队列下一个要出队的节点应该是head.nextnext为null,代表这个节点是队列中的最后一个节点
因此节点的next不仅仅表示链表关系,并且承载了节点生命周期状态。
内部数据域
java
/** The capacity bound, or Integer.MAX_VALUE if none */
private final int capacity;
/** Current number of elements */
private final AtomicInteger count = new AtomicInteger();
/**
* Head of linked list.
* Invariant: head.item == null
*/
transient Node<E> head;
/**
* Tail of linked list.
* Invariant: last.next == null
*/
private transient Node<E> last;
/** Lock held by take, poll, etc */
private final ReentrantLock takeLock = new ReentrantLock();
/** Wait queue for waiting takes */
@SuppressWarnings("serial") // Classes implementing Condition may be serializable.
private final Condition notEmpty = takeLock.newCondition();
/** Lock held by put, offer, etc */
private final ReentrantLock putLock = new ReentrantLock();
/** Wait queue for waiting puts */
@SuppressWarnings("serial") // Classes implementing Condition may be serializable.
private final Condition notFull = putLock.newCondition();
capacity用于指定队列的最大容量。count用于确定当前队列中的元素个数,使用AtomicInteger是因为入队和出队使用的锁不是同一把,无法用一把锁来保护count。head是哨兵节点,last是尾节点takeLock用于出队的同步控制,putLock用于入队的同步控制。- 条件变量
notEmpty当队列为空的时候,出队线程阻塞,条件变量notFull,当队列满时,入队线程等待。
构造方法
java
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);
}
在无参构造的情况下,队列的大小是Integer.MAX_VALUE。
LinkedBlockingQueue的操作
offer方法
java
public boolean offer(E e) {
if (e == null) throw new NullPointerException();
final AtomicInteger count = this.count;
if (count.get() == capacity)
return false;
final int c;
final Node<E> node = new Node<E>(e);
final ReentrantLock putLock = this.putLock;
putLock.lock();
try {
if (count.get() == capacity)
return false;
enqueue(node);
c = count.getAndIncrement();
if (c + 1 < capacity)
notFull.signal();
} finally {
putLock.unlock();
}
if (c == 0)
signalNotEmpty();
return true;
}
offer方法是非阻塞方法,如果队列未满,则入队成功,返回true,否则入队失败,返回false。
- 先预检
count,如果队列已满,可以减少不必要的加锁流程。 - 获取
putLock,并再次检查容量 - 节点入队
- 容量原子性增加,并且如果入队后仍然未满,唤醒一个
put线程 - 如果原先容量为0,唤醒
take线程
注意我们说offer是非阻塞方法,是指它不会阻塞在条件队列中,但是可能在竞争锁时阻塞在同步队列中。
put方法
java
/**
* Inserts the specified element at the tail of this queue, waiting if
* necessary for space to become available.
*
* @throws InterruptedException {@inheritDoc}
* @throws NullPointerException {@inheritDoc}
*/
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.
*/
while (count.get() == capacity) {
notFull.await();
}
enqueue(node);
c = count.getAndIncrement();
if (c + 1 < capacity)
notFull.signal();
} finally {
putLock.unlock();
}
if (c == 0)
signalNotEmpty();
}
put方法是一个阻塞方法,并且可被中断。
- 加可中断的锁
putLock.lockInterruptibly()。 - 当队列满时,阻塞直到被唤醒
- 节点入队
- 如果入队之后还有空余,唤醒其他
put线程 - 如果入队前,队列为空,唤醒其他
take线程
poll方法
java
public E poll() {
final AtomicInteger count = this.count;
if (count.get() == 0)
return null;
final E x;
final int c;
final ReentrantLock takeLock = this.takeLock;
takeLock.lock();
try {
if (count.get() == 0)
return null;
x = dequeue();
c = count.getAndDecrement();
if (c > 1)
notEmpty.signal();
} finally {
takeLock.unlock();
}
if (c == capacity)
signalNotFull();
return x;
}
poll是非阻塞式出队。
- 同样是不加锁检查容量,当容量为0时快速失败
- 获取
takeLock锁,并再次检查容量是否为0 - 元素出队
- 容量原子性减少,如果容量大于0,唤醒其他
take线程 - 当容量之前是满时,唤醒
put线程
take方法
java
public E take() throws InterruptedException {
final E x;
final int c;
final AtomicInteger count = this.count;
final ReentrantLock takeLock = this.takeLock;
takeLock.lockInterruptibly();
try {
while (count.get() == 0) {
notEmpty.await();
}
x = dequeue();
c = count.getAndDecrement();
if (c > 1)
notEmpty.signal();
} finally {
takeLock.unlock();
}
if (c == capacity)
signalNotFull();
return x;
}
take是阻塞式取元素方法。
- 可中断式获取锁。
- 当容量为0时阻塞
- 元素出队
- 容量原子性递减
- 如果容量有剩余,唤醒其他
take线程 - 如果容量之前是满的,唤醒其他
put线程