LinkedBlockQueue 源码分析
LinkedBlockQueue 继承关系
- Collection
- AbstractCollection
- AbstractQueue
- LinkedBlockingQueue
- AbstractCollection
LinkedBlockQueue 还实现了 BlockingQueue 接口。 通过继承关系可以知道 LinkedBlockQueue 是一个容器类,所以就主要分析他的数据封装方式和对数据的管理方式。
BlockingQueue 接口的一些方法
博客围绕 LinkedBlockQueue 对 BlockingQueue 的方法的一些实现进行分析, 因为LinkedBlockQueue 继承自 AbstractQueue 且 AbstractQueue 中 add() 内部调用了 offer(), remove() 方法内部调用了 poll(),element() 调用了 peek(), 所以针对LinkedBlockQueue 主要分析这三个方法
public interface BlockingQueue<E> extends Queue<E> {
boolean add(E e);
boolean offer(E e);
void put(E e) throws InterruptedException;
boolean offer(E e, long timeout, TimeUnit unit)
throws InterruptedException;
E take() throws InterruptedException;
E poll(long timeout, TimeUnit unit)
throws InterruptedException;
boolean remove(Object o);
boolean contains(Object o);
}
LinkedBlockQueue 源码分析
offer() 方法
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;
}
/**
* Linked list node class.
*/
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; }
}
- 如果LinkedBlockQueue 设置了 capacity 则先判断当前队列是否已满,可以在LinkedBlockQueue中设置容量大小,默认值是 Integer.MAX_VALUE.
- 数据会封装到Node节点中
- 获取 putLock 并执行入队操作
- 如果队列未满,则唤醒put等待线程(不一定唤醒,因为没有Condition.await())的情况下也会调用
- offer() 方法是线程安全的方法
/**
* 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;
}
入队操作是设置当前 last.next = node 且 last 指向当前 Node. 所以 LinkedBlockQueue 是一个双端队列,first 指向队头, last 指向队尾。取的时候从队头取,插入时往队尾插入。
poll() 方法
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() 返回 null
- 获取 takeLock, 通过上面 offer() 方法知道,LinkedBlockQueue 维护了两个 ReentrantLock, 两个维度同步队列的获取和插入操作
- dequeue() 从队头获取数据
/**
* 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;
}
上面的方法没有讲清楚 LinkedBlockQueue 的阻塞关系。所以需要看另外两个方法。
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();
final int c;
final Node<E> node = new Node<E>(e);
final ReentrantLock putLock = this.putLock;
final AtomicInteger count = this.count;
putLock.lockInterruptibly();
try {
while (count.get() == capacity) {
notFull.await();
}
enqueue(node);
c = count.getAndIncrement();
if (c + 1 < capacity)
notFull.signal();
} finally {
putLock.unlock();
}
if (c == 0)
signalNotEmpty();
}
这个方法比 offer() 多了一段代码, 就是当 count.get() == capacity 时,调用 notFull.await() 方法,就是说当队列满的时候要想再插入数据, 就需要等待队列取出数据。
take() 方法
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;
}
这个方法比 poll() 方法多了 count.get() == 0 时阻塞等待的过程。
总结
- LinkedBlockQueue 是一个双端链表的数据结构,队尾 last 插入数据,队头 head 取出数据
- LinkedBlockQueue 是线程安全的数据结构
- offer()和poll() 对应, put()和take() 对应且能满足生产者消费者模型