LinkedBlockingQueue
基于单向链表实现的阻塞队列
属性
/** 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 */
// 队列元素不为空时,唤醒阻塞线程(队列为空时获取元素线程阻塞)
private final Condition notEmpty = takeLock.newCondition();
/** Lock held by put, offer, etc */
// 添加元素时需要获取的锁
private final ReentrantLock putLock = new ReentrantLock();
/** Wait queue for waiting puts */
// 队列元素不满时唤醒阻塞线程(队列已满时添加元素线程阻塞)
private final Condition notFull = putLock.newCondition();
构造方法
// 无参构造函数,默认容量为Integer.MAX_VALUE
public LinkedBlockingQueue() {
this(Integer.MAX_VALUE);
}
/**
* Creates a {@code LinkedBlockingQueue} with the given (fixed) capacity.
*
* @param capacity the capacity of this queue
* @throws IllegalArgumentException if {@code capacity} is not greater
* than zero
*/
public LinkedBlockingQueue(int capacity) {
if (capacity <= 0) throw new IllegalArgumentException();
this.capacity = capacity;
//初始化头、尾节点。值为null
last = head = new Node<E>(null);
}
/**
* Linked list node class
*/
static class Node<E> {
// Node节点保存的值
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; }
}
重要方法
offer
public boolean offer(E e) {
// 阻塞队列不能传入 null 元素
if (e == null) throw new NullPointerException();
final AtomicInteger count = this.count;
// 队列已满, 直接返回false
if (count.get() == capacity)
return false;
int c = -1;
// 将传入的值封装为节点
Node<E> node = new Node<E>(e);
// 获取锁
final ReentrantLock putLock = this.putLock;
putLock.lock();
try {
//队列容器元素未满
if (count.get() < capacity) {
//入队
enqueue(node);
//获取元素数量
c = count.getAndIncrement();
// 元素数量 小于 容量
if (c + 1 < capacity)
notFull.signal();
}
} finally {
putLock.unlock();
}
if (c == 0)
// 唤醒被阻塞线程
signalNotEmpty();
return c >= 0;
}
元素入队
/**
* Links node at end of queue.
*
* @param node the node
*/
private void enqueue(Node<E> node) {
// assert putLock.isHeldByCurrentThread();
// assert last.next == null;
last = last.next = node;
}
判断在添加元素之前,队列是否为空。如果为空那么唤醒 Condition
队列中被阻塞线程
if (c == 0)
signalNotEmpty();
如果 c==0
成立,说明在此添加元素操作之前,队列为空,那么如果有其他线程要移除此队列元素就会被阻塞。向此队列中添加元素过后 队列非空,就需要唤醒被阻塞线程(如果存在此被阻塞线程的话)
/**
* Signals a waiting take. Called only from put/offer (which do not
* otherwise ordinarily lock takeLock.)
*/
private void signalNotEmpty() {
final ReentrantLock takeLock = this.takeLock;
takeLock.lock();
try {
// 唤醒被阻塞线程
notEmpty.signal();
} finally {
takeLock.unlock();
}
}
signalNotEmpty 方法只能由 put/offer 调用。 add 方法内部调用了offer
put
/**
* 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();
// Note: convention in all put/take/etc is to preset local var
// holding count negative to indicate failure unless set.
int c = -1;
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 方法和 offer方法基本一致,区别在于队列已满时线程阻塞
while (count.get() == capacity) {
notFull.await();
}
poll
public E poll() {
final AtomicInteger count = this.count;
// 队列中不存在元素
if (count.get() == 0)
return null;
E x = null;
int c = -1;
// 获取锁
final ReentrantLock takeLock = this.takeLock;
takeLock.lock();
try {
if (count.get() > 0) {
// 元素出队
x = dequeue();
// 元素数量减一
c = count.getAndDecrement();
if (c > 1)
// 唤醒其他阻塞队列
notEmpty.signal();
}
} finally {
takeLock.unlock();
}
// 如果 元素数量等于 队列的容量
if (c == capacity)
// Signals a waiting put. Called only from take/poll.
signalNotFull();
return x;
}
移除队列元素
/**
* Removes a node from head of queue.
*
* @return the node
*/
private E dequeue() {
// assert takeLock.isHeldByCurrentThread();
// assert head.item == null;
Node<E> h = head;
Node<E> first = h.next;
h.next = h; // help GC
head = first;
E x = first.item;
first.item = null;
return x;
}
take
public E take() throws InterruptedException {
E x;
int c = -1;
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;
}
同样的线程
对比 ArrayBlockingQueue
ArrayBlockingQueue:
- 数组
Object[] items
存储 - 元素的添加和移除通过 两个索引来控制
takeIndex
、putIndex
- 使用一个普通变量存储容器中元素的数量
int count;
- 添加和移除元素 使用同一个锁
final ReentrantLock lock;
- 每次移除元素 调用
notFull.signal()
- 每次添加元素 调用
notEmpty.signal()
LinkedBlockingQueue:
Node
节点存储元素- 元素的添加移除 通过
head/last
节点的指向控制 - 通过一个原子操作获取容器元素的数量
final AtomicInteger count = new AtomicInteger();
- 使用两个锁,移除元素
private final ReentrantLock takeLock
添加元素private final ReentrantLock putLock
- 添加元素、移除元素 走双重判断唤醒
LinkedBlockingQueue中的移除和添加元素的方法,都进行了两步操作。
添加元素
final ReentrantLock putLock = this.putLock;
putLock.lock();
try {
if (count.get() < capacity) {
enqueue(node);
c = count.getAndIncrement();
// 判断队列是否已满
if (c + 1 < capacity)
// putLock
notFull.signal();
}
} finally {
putLock.unlock();
}
//判断队列是否为空
if (c == 0)
// takeLock
signalNotEmpty();
移除元素
final ReentrantLock takeLock = this.takeLock;
takeLock.lock();
try {
if (count.get() > 0) {
x = dequeue();
c = count.getAndDecrement();
if (c > 1)
//takeLock
notEmpty.signal();
}
} finally {
takeLock.unlock();
}
if (c == capacity)
//putLock
signalNotFull();
LinkedBlockingQueue的双重判断是什么原因呢:
- 由于链表的特性所以 使用了两个锁,来优化队列操作的效率
- 使用了两个锁也带来了新的问题,队列的操作也需要获取两个锁。
- 元素移除首先获取锁 takeLock,先移除元素,然后再获取锁putLock,唤醒因队列已满而被阻塞的线程(put/offer)。
- 这样会导致明明队列未满,却依旧需要不断的试图唤醒被阻塞线程。所以执行了一个判断
if (c == capacity)
当满足指定条件时需要唤醒线程(队列未满)。 - 这样又存在了另一个问题。假设现在队列已满 同时存在两个因put操作阻塞的线程,那么执行
take
操作之后 signalNotFull 只会唤醒一个线程。所以需要另一个操作来唤醒其他被阻塞线程if (c + 1 < capacity) // putLock notFull.signal();