LinkedBlockingQueue简介
①LinkedBlockingQueue底层数据结构单向链表
②获取元素按照FIFO原则
③头尾通过独立的锁来控制并发
④可以构建时指定容量,默认为Integer.MAX_VALUE,无界队列,使用时注意内存溢出问题!
LinkedBlockingQueue内部属性
// 链表结点结构
static class Node<E> {
E item;
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; // 链表尾结点
// 头尾并发控制锁
private final ReentrantLock takeLock = new ReentrantLock();
private final Condition notEmpty = takeLock.newCondition();
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(); // put元素非空
int c = -1;
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); // last = last.next = node;这做了两个操作,尾结点next指向node,将node置为last
c = count.getAndIncrement();// 此处c是元素入队前队列中个数
if (c + 1 < capacity) // 入队后队列仍没到最大容量,唤醒put阻塞线程
notFull.signal();
} finally {
putLock.unlock();
}
if (c == 0) // 该元素入队前队列为空(不包括head空节点),此时可能之前有已经被阻塞的take线程,唤醒take阻塞线程
signalNotEmpty();
}
public boolean offer(E e, long timeout, TimeUnit unit)
throws InterruptedException {
if (e == null) throw new NullPointerException(); // 元素非空
long nanos = unit.toNanos(timeout);
int c = -1;
final ReentrantLock putLock = this.putLock;
final AtomicInteger count = this.count;
putLock.lockInterruptibly();
try {
while (count.get() == capacity) {
if (nanos <= 0) // 与上述方法区别: 阻塞指定时间,到期后队列仍满,返回false
return false;
nanos = notFull.awaitNanos(nanos);
}
enqueue(new Node<E>(e));
c = count.getAndIncrement();
if (c + 1 < capacity)
notFull.signal();
} finally {
putLock.unlock();
}
if (c == 0)
signalNotEmpty();
return true; // 添加成功返回值true
}
public boolean offer(E e) { // 该方法添加失败,直接返回false
if (e == null) throw new NullPointerException();
final AtomicInteger count = this.count;
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(); // 入队后容量未达最大值,唤醒阻塞的put线程
}
} finally {
putLock.unlock();
}
if (c == 0)
signalNotEmpty();
return c >= 0;
}
出队方法
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) // 出队前队列已满,那么可能存在被阻塞的put线程。唤醒一次
signalNotFull();
return x;
}
public E poll(long timeout, TimeUnit unit) throws InterruptedException {
E x = null;
int c = -1;
long nanos = unit.toNanos(timeout);
final AtomicInteger count = this.count;
final ReentrantLock takeLock = this.takeLock;
takeLock.lockInterruptibly();
try {
while (count.get() == 0) {
if (nanos <= 0)
return null;
nanos = notEmpty.awaitNanos(nanos);
}
x = dequeue();
c = count.getAndDecrement();
if (c > 1)
notEmpty.signal();
} finally {
takeLock.unlock();
}
if (c == capacity)
signalNotFull();
return x;
}
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)
signalNotFull();
return x;
}
peek获取第一个节点(不包含head结点)
public E peek() {
if (count.get() == 0) // 队列为空,返回空
return null;
final ReentrantLock takeLock = this.takeLock;
takeLock.lock();
try {
Node<E> first = head.next; // peek获取的是head后的第一个节点
if (first == null)
return null;
else
return first.item;
} finally {
takeLock.unlock();
}
}
unlink(Node<E> p, Node<E> trail)移除指定节点
参数p,待移除节点;参数trail为p的前继节点
void unlink(Node<E> p, Node<E> trail) {
p.item = null; // 置空p的item
trail.next = p.next; // p的前继结点指向p的后继结点
if (last == p)
last = trail; // 如果p是尾结点,则trail为last尾结点
if (count.getAndDecrement() == capacity)
notFull.signal(); // 队列元素个数到达最大容量,唤醒take消费线程
}
remove移除指定item值的节点
public boolean remove(Object o) {
if (o == null) return false;
fullyLock();
try {
for (Node<E> trail = head, p = trail.next;
p != null;
trail = p, p = p.next) {// 每次trail都记录前继结点
if (o.equals(p.item)) {
unlink(p, trail);
return true;
}
}
return false;
} finally {
fullyUnlock();
}
}
LinkedBlockingQueue迭代器:Iterator和LBQSpliterator
Iterator在LinkedBlockingQueue中描述过,简单就不说了。主要描述下LBQSpliterator的源码。
可拆分迭代器的 tryAdvance、forEachRemaining、trySplit方法都是需要获取fullLock的,所以注意对吞吐量的影响,tryAdvance获取第一个item不为空的节点数据做指定的操作,forEachRemaining循环遍历当前迭代器中所有没有被移除的节点(item不为空)做指定的操作。
public Spliterator<E> trySplit() {
Node<E> h;
final LinkedBlockingQueue<E> q = this.queue;
int b = batch; // 初始0
int n = (b <= 0) ? 1 : (b >= MAX_BATCH) ? MAX_BATCH : b + 1; // 从1开始,不断累加,直到最大1 << 25
if (!exhausted && // exhausted用于判断遍历的结点是否已到尾结点
((h = current) != null || (h = q.head.next) != null) &&
h.next != null) {
Object[] a = new Object[n];
int i = 0;
Node<E> p = current;
q.fullyLock();
try {
if (p != null || (p = q.head.next) != null) {
do {
if ((a[i] = p.item) != null) // 非空元素添加到数组a
++i;
} while ((p = p.next) != null && i < n);
}
} finally {
q.fullyUnlock();
}
if ((current = p) == null) { // current = p记录上一次遍历到的节点。如果为空,说明已经到达尾结点
est = 0L; // 队列中还剩多少元素未遍历,此处0表示遍历完了
exhausted = true;
}
else if ((est -= i) < 0L) // 队列中全部结点已遍历完了
est = 0L;
if (i > 0) {
batch = i; // 当前拆分的个数
return Spliterators.spliterator
(a, 0, i, Spliterator.ORDERED | Spliterator.NONNULL |
Spliterator.CONCURRENT);
// ORDERED有序、NONNULL非空、CONCURRENT表示可以同时修改源,即我们可以使用多个线程并发地添加,修改或删除元素,而无需同步。
}
}
return null;
}
public void forEachRemaining(Consumer<? super E> action) {
if (action == null) throw new NullPointerException();
final LinkedBlockingQueue<E> q = this.queue;
if (!exhausted) {
exhausted = true;
Node<E> p = current;
do {
E e = null;
q.fullyLock();
try {
if (p == null)
p = q.head.next;
while (p != null) {
e = p.item;
p = p.next;
if (e != null)
break;
}
} finally {
q.fullyUnlock();
}
if (e != null)
action.accept(e);
} while (p != null);
}
}
public boolean tryAdvance(Consumer<? super E> action) {
if (action == null) throw new NullPointerException();
final LinkedBlockingQueue<E> q = this.queue;
if (!exhausted) {
E e = null;
q.fullyLock();
try {
if (current == null)
current = q.head.next;
while (current != null) {
e = current.item;
current = current.next;
if (e != null)
break;
}
} finally {
q.fullyUnlock();
}
if (current == null)
exhausted = true;
if (e != null) {
action.accept(e);
return true;
}
}
return false;
}
LinkedBlockingQueue的迭代器拆分很特别,它不是像ArrayBlockingQueue那样每次分一半,而是第一次只拆一个元素,第二次拆2个,第三次拆三个,依次内推,拆分的次数越多,后面的迭代器分的得元素越多,直到一个很大的数MAX_BATCH ,后面的迭代器每次都分到这么多的元素,拆分的实现逻辑很简单,每一次拆分结束都记录下拆分到哪个元素,下一次拆分从上次结束的位置继续往下拆分,直到没有元素可拆分了返回null。