Java的阻塞容器介绍(JDK1.8)
先来看看阻塞容器和其他容器之间的层级关系
- Collection
- AbstractCollection
- Queue
- BlockingQueue
- AbstractQueue
- ArrayBlockingQueue
- LinkedBlockingQueue
- SynchronousQueue
- PriorityBlockingQueue
我们就挑这四个重要的实现类来讲解。
一、ArrayBlockingQueue
1.类的定义
public class ArrayBlockingQueue<E> extends AbstractQueue<E>
implements BlockingQueue<E>, java.io.Serializable
正如上面层级显示的,java的容器都是支持泛型的,可序列化的
2.重要的成员变量
/** The queued items */
final Object[] items;// 核心数组
/** items index for next take, poll, peek or remove */
int takeIndex;// 出队索引,出队操作都会使用
/** items index for next put, offer, or add */
int putIndex;// 入队索引, 入队操作都会使用
/** Number of elements in the queue */
int count;// 集合中元素的个数
/*
* Concurrency control uses the classic two-condition algorithm
* found in any textbook.
*/
/** Main lock guarding all access */
final ReentrantLock lock;// 并发的关键,是一个final的可重入锁
/** Condition for waiting takes */
private final Condition notEmpty;// 非空条件(用于通知消费者消费)
/** Condition for waiting puts */
private final Condition notFull;// 非满条件(用于通知生产者生产)
/**
* Shared state for currently active iterators, or null if there
* are known not to be any. Allows queue operations to update
* iterator state.
*/
transient Itrs itrs = null;// 这个Itrs是一个内部类,内部包装了一个迭代器
关于Condition对象,它有两个重要的方法signal(),await(),用于当满足条件时,对相关线程的唤醒和等待,而且要创建Condition的对象需要调用Lock的newCondition(),方法会new一个ConditionObject作为默认的实现,ConditonObject是个AQS的内部类,一般这么使用就行了。
可以直接理解为Object对象中的wait(), notify()方法,但是Condition对象可以将对线程的唤醒等待进行更细化的管理,但是条件是必须用在Lock对象的lock(),unlock()方法之间。
是现在线程之间通信更推荐的方法。
3.初始化
ArrayBlockingQueue 提供了三个构造器供用户使用
public ArrayBlockingQueue(int capacity) {
this(capacity, false);// 直接调用了重载的版本,默认非公平锁
}
public ArrayBlockingQueue(int capacity, boolean fair) {
if (capacity <= 0)// 对传入的容量参数进行合法的校验
throw new IllegalArgumentException();
this.items = new Object[capacity];// 从这里以及名字上可以看到这个ArrayBlockingQueue底层就是一个数组
this.lock = new ReentrantLock(fair);// 这个容器的支持并发也基于这个重要的变量lock
this.notEmpty = lock.newCondition();// 以及两个条件变量Condition
this.notFull = lock.newCondition();
}
public ArrayBlockingQueue(int capacity, boolean fair,
Collection<? extends E> c) {
this(capacity, fair);// 先调用了重载版本初始化对象
final ReentrantLock lock = this.lock;
lock.lock(); // Lock only for visibility, not mutual exclusion,这句的意思是虽然不一定需要锁,但还是显式的写了出来
// 由于参数里有集合,需要将参数集合中的元素添加至本集合,所以显式的锁住整个集合,再进行元素的添加
try {
int i = 0;
try {
for (E e : c) {
checkNotNull(e);// 非空校验
items[i++] = e;// 由于是数组所以直接可以通过索引赋值即可
}
} catch (ArrayIndexOutOfBoundsException ex) {
throw new IllegalArgumentException();
}
this.count = i;// 记录元素的总count数
this.putIndex = (i == capacity) ? 0 : i;// 记录“入队索引”
} finally {
lock.unlock();
}
}
从上面的三个构造器来看,可以知道ArrayBlockingQueue是一个基于数组的阻塞并发队列,并且在初始化的时候必须指定整个容器的大小(也就是成员变量数组的大小),并且后面也会知道,整个容器是不会扩容的,并且默认使用的是非公平锁。
4.一些重要的非公开的方法
这些方法封装了一些操作,在相关公开方法中会直接去调用
/**
* Returns item at index i.
*/
@SuppressWarnings("unchecked")
final E itemAt(int i) {
return (E) items[i];// 直接返回索引i处的对象
}
/**
* Throws NullPointerException if argument is null.
*
* @param v the element
*/
private static void checkNotNull(Object v) {
if (v == null)// 非空的校验,null的话就会抛异常
throw new NullPointerException();
}
/**
* Inserts element at current put position, advances, and signals.
* Call only when holding lock.
*/
private void enqueue(E x) {// 其实就是入队操作
// assert lock.getHoldCount() == 1;
// assert items[putIndex] == null;
final Object[] items = this.items;
items[putIndex] = x;// 把当前的“入队索引”处赋值成传入参数
if (++putIndex == items.length)// 如果“索引”超过了长度就归零
putIndex = 0;
count++;// 元素数目增加
notEmpty.signal();// 并且通知消费线程消费
}
/**
* Extracts element at current take position, advances, and signals.
* Call only when holding lock.
*/
private E dequeue() {// 出队操作
// assert lock.getHoldCount() == 1;
// assert items[takeIndex] != null;
final Object[] items = this.items;
@SuppressWarnings("unchecked")
E x = (E) items[takeIndex];// 先获取“出队索引”出的元素用于返回
items[takeIndex] = null;// 将该索引处置空
if (++takeIndex == items.length)// 同入队
takeIndex = 0;
count--;// 元素数目减少
if (itrs != null)
itrs.elementDequeued();
notFull.signal();// 通知生产线程可以继续生产
return x;// 将删除的元素返回
}
/**
* Deletes item at array index removeIndex.
* Utility for remove(Object) and iterator.remove.
* Call only when holding lock.
*/
void removeAt(final int removeIndex) {
// assert lock.getHoldCount() == 1;
// assert items[removeIndex] != null;
// assert removeIndex >= 0 && removeIndex < items.length;
final Object[] items = this.items;
if (removeIndex == takeIndex) {// 这个if和出队方法 dequeue 基本一致
// removing front item; just advance
items[takeIndex] = null;
if (++takeIndex == items.length)
takeIndex = 0;
count--;
if (itrs != null)
itrs.elementDequeued();
} else {
// an "interior" remove
// slide over all others up through putIndex.
// 当需要删除的索引和当前的出队索引不一致时才会执行这里的逻辑
final int putIndex = this.putIndex;
for (int i = removeIndex;;) {// 这里的循环就是把当前要删除的索引之后的所有元素向前挪动一个索引的位置
int next = i + 1;
if (next == items.length)
next = 0;
if (next != putIndex) {
items[i] = items[next];
i = next;
} else {
items[i] = null;// 置空最后的索引处
this.putIndex = i;// 并且在最后把“入队索引”放在被置空索引的索引处
break;
}
}
count--;
if (itrs != null)
itrs.removedAt(removeIndex);// 关于itrs这个成员在最后讲解下
}
notFull.signal();// 由于删除了元素,虽然不是出队的操作,但是也通知生产线程可以继续生产了
}
5.入队和出队操作(重要)
1.入队操作(add, offer, put )
public boolean add(E e) {
return super.add(e);
}
可以看到ArrayBlockingQueue的add方法是直接调用父类AbstractQueue的add方法的
public boolean add(E e) {
if (offer(e))// 直接调用的offer方法
return true;
else // 从这里也可以看到 add方法当没有添加成功的时候是会抛异常的,并提示“队列满”
throw new IllegalStateException("Queue full");
}
让我们继续看看offer方法
public boolean offer(E e) {
checkNotNull(e);// 添加的元素不能为空
final ReentrantLock lock = this.lock;
lock.lock();// 由于ArrayBlockingQueue是只有一把锁,所以lock就是锁住整个集合
try {
if (count == items.length)// 由于整个集合是个数组所以当元素数目和数组长度一致时,说明队列已满
return false;
else {
enqueue(e);// 调用之前的入队方法添加元素
return true;
}
} finally {
lock.unlock();// 最后不能忘记释放锁
}
}
// offer方法的另一个重载版本, 多了一个等待的时间和单位
public boolean offer(E e, long timeout, TimeUnit unit)
throws InterruptedException {
checkNotNull(e);
long nanos = unit.toNanos(timeout);// 这里会将入参的时间和单位转换成纳秒
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == items.length) {
if (nanos <= 0)
return false;
nanos = notFull.awaitNanos(nanos);// 在这里会等待对应的纳秒数,时间到了线程会醒来,而并非一直阻塞
}
enqueue(e);
return true;
} finally {
lock.unlock();
}
}
还有put方法
public void put(E e) throws InterruptedException {
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();// 这里有区别是因为put方法是会一直阻塞到能够添加位置,所以这里并没有用传统的lock()方法,因为传统的lock() 方法会无视线程的中断信号一直会尝试获取锁
// 所以这里的lockInterruptibly()方法是一个可中断锁,避免了在收到了中断信号后,线程仍然阻塞
try {
while (count == items.length)
notFull.await();// 当队列已满的时候,等待出队操作给出的信号
enqueue(e);// 当被唤醒后,说明队列已经空出了位置,可以继续入队
} finally {
lock.unlock();
}
}
方法名 | 备注 |
---|---|
add | 添加成功返回true,否则抛出异常 |
offer | 添加成功返回true,否则返回false (常用,推荐) |
put | 当队列已满的时候,线程阻塞,等待消费者消费通知,直到入队成功 |
2.出队操作(poll, peek, take, remove)
public E poll() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return (count == 0) ? null : dequeue();// 如果队列中没有元素直接返回null,否则就调用出队方法dequeue
} finally {
lock.unlock();
}
}
// 同offer,也有个重载版本的出队操作
public E poll(long timeout, TimeUnit unit) throws InterruptedException {
long nanos = unit.toNanos(timeout);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0) {
if (nanos <= 0)
return null;
nanos = notEmpty.awaitNanos(nanos);
}
return dequeue();
} finally {
lock.unlock();
}
}
public E peek() {
final ReentrantLock lock = this.lock;
lock.lock();
try { // 和poll的区别是,peek方法只是返回处于队列首的元素,并不将它移除出队列
return itemAt(takeIndex); // null when queue is empty
} finally {
lock.unlock();
}
}
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0) // take方法和put方法类似,都是使用中断锁,并且会一直阻塞,直到之后的出队(入队)操作成功
notEmpty.await();
return dequeue();
} finally {
lock.unlock();
}
}
让我们再来看看remove方法
public boolean remove(Object o) {
if (o == null) return false; // 这样的阻塞队列是不能有null元素的,所以null直接返回false
final Object[] items = this.items; // 获得数组
final ReentrantLock lock = this.lock;
lock.lock();
try {
if (count > 0) {
final int putIndex = this.putIndex;
int i = takeIndex;
do { // 这里的循环就是从“出队索引”开始遍历,通过equals比较,找到了就删除
if (o.equals(items[i])) {
removeAt(i);
return true;
}
if (++i == items.length) // 这里因为无论“入队索引”还是“出队索引”都是向上加的,只要加到了,数组长度都会归零,从索引0重新开始
i = 0;
} while (i != putIndex);// 因为take和put 索引可以说相当于这个数组的“头”和“尾”,所以遍历到“入队索引”时,说明整个数组都遍历完了,就结束循环
}
return false;
} finally {
lock.unlock();
}
}
方法名 | 备注 |
---|---|
poll | 移除成功返回true,否则返回false (常用,推荐) |
peek | 只返回队列最前面的元素,并不移除它 |
take | 当队列空的时候,线程阻塞,等待生产者生产通知,直到出队成功 |
remove | 有入参,可以移除指定的元素,但是会对集合进行遍历,效率低(移除是否成功和重写的equals相关) |
6.其他方法
至此,这个阻塞容器的重要方法已经介绍完毕了,现在对其他方法进行依次介绍
public int size() {
final ReentrantLock lock = this.lock;
lock.lock();
try { // 由于只有一把锁,直接锁住整个集合,返回count值就行了
return count;
} finally {
lock.unlock();
}
}
// 返回的是集合中剩余的容量
public int remainingCapacity() {
final ReentrantLock lock = this.lock;
lock.lock();
try { // 同理,锁住整个集合
return items.length - count; // 返回数组长度 减去 元素个数,就是剩余的容量了
} finally {
lock.unlock();
}
}
// 和remove几乎一样
public boolean contains(Object o) {
if (o == null) return false;
final Object[] items = this.items;
final ReentrantLock lock = this.lock;
lock.lock();
try {
if (count > 0) {
final int putIndex = this.putIndex;
int i = takeIndex;
do {
if (o.equals(items[i]))
return true; // 只是找到对应元素不需要进行删除操作,直接返回true就行了
if (++i == items.length)
i = 0;
} while (i != putIndex);
}
return false;
} finally {
lock.unlock();
}
}
public Object[] toArray() {
Object[] a;
final ReentrantLock lock = this.lock;
lock.lock();
try {
final int count = this.count;
a = new Object[count];// new了一个和count相等长度的数组
int n = items.length - takeIndex;
if (count <= n) // 通过数组复制完成数据从集合到数组的迁移
System.arraycopy(items, takeIndex, a, 0, count);
else {
System.arraycopy(items, takeIndex, a, 0, n);
System.arraycopy(items, 0, a, n, count - n);
}
} finally {
lock.unlock();
}
return a;
}
public <T> T[] toArray(T[] a) {
final Object[] items = this.items;
final ReentrantLock lock = this.lock;
lock.lock();
try {
final int count = this.count;
final int len = a.length;
if (len < count)
a = (T[])java.lang.reflect.Array.newInstance(// 这里的区别就是通过反射,获取入参数组的类型,来创建对应类型的新数组
a.getClass().getComponentType(), count);
int n = items.length - takeIndex;
if (count <= n)
System.arraycopy(items, takeIndex, a, 0, count);
else {
System.arraycopy(items, takeIndex, a, 0, n);
System.arraycopy(items, 0, a, n, count - n);
}
if (len > count)
a[count] = null;// 并且把之后的count处的索引置空
} finally {
lock.unlock();
}
return a;
}
public String toString() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
int k = count;
if (k == 0)// 如果是空的话直接打个括号
return "[]";
final Object[] items = this.items;
StringBuilder sb = new StringBuilder();
sb.append('[');
for (int i = takeIndex; ; ) {// 遍历完每个元素,都在后面加个逗号和空格
Object e = items[i];
sb.append(e == this ? "(this Collection)" : e);
if (--k == 0) // 最后一个元素就直接 反括号结束
return sb.append(']').toString();
sb.append(',').append(' ');
if (++i == items.length)
i = 0;
}
} finally {
lock.unlock();
}
}
public void clear() {
final Object[] items = this.items;
final ReentrantLock lock = this.lock;
lock.lock();
try {
int k = count;
if (k > 0) {
final int putIndex = this.putIndex;
int i = takeIndex;
do {
items[i] = null; // 通过遍历,将数组中的每一项都置空
if (++i == items.length)
i = 0;
} while (i != putIndex);// 当出队和入队索引一样的时候就是说明所有元素都遍历到了
takeIndex = putIndex;
count = 0;// 清零count
if (itrs != null)
itrs.queueIsEmpty(); // 对内部类(其中的迭代器)归零
for (; k > 0 && lock.hasWaiters(notFull); k--)
notFull.signal();// 如果仍然有生产者线程在等待,唤醒它
}
} finally {
lock.unlock();
}
}
// 这两个方法放在一起说了
public int drainTo(Collection<? super E> c) {
return drainTo(c, Integer.MAX_VALUE);
}
// 这个方法就是把本集合中的元素转移到入参集合c中
public int drainTo(Collection<? super E> c, int maxElements) {
checkNotNull(c);
if (c == this)
throw new IllegalArgumentException();
if (maxElements <= 0)
return 0;
final Object[] items = this.items;
final ReentrantLock lock = this.lock;
lock.lock();
try {
int n = Math.min(maxElements, count);
int take = takeIndex;
int i = 0;
try {
while (i < n) {
@SuppressWarnings("unchecked")
E x = (E) items[take];
c.add(x);// 添加到集合c中,但是如果add抛异常了,比如添加的目标集合也是一个有界的集合,那之前的添加成功的元素会转移到新集合中,原有的集合中的这些元素都会被移除,但是添加失败和它之后的元素,都仍然会保留在本集合中
items[take] = null;
if (++take == items.length)
take = 0;
i++;
}
return n;
} finally {
// Restore invariants even if c.add() threw
// 和clear()方法类似,将集合的参数都归零
if (i > 0) {
count -= i;
takeIndex = take;
if (itrs != null) {
if (count == 0)
itrs.queueIsEmpty();
else if (i > take)
itrs.takeIndexWrapped();
}
for (; i > 0 && lock.hasWaiters(notFull); i--)
notFull.signal();
}
}
} finally {
lock.unlock();
}
}
// 返回迭代器
public Iterator<E> iterator() {
return new Itr();
}
细心的读者可能观察到,之前有个成员变量Itrs 一直没怎么用到过,而且也没看到它在哪里初始化。那点开源码发现,这个Itrs究竟是什么呢?并且它是在哪里进行的初始化呢?其实它是在ArrayBlockingQueue的迭代器中初始化的,也就是说你如果用到了iterator()方法,才会对Itrs进行初始化。那既然这样,就让我们先来看看迭代器Itr吧。
内部类(迭代器)
类的定义
private class Itr implements Iterator<E> // 就是个内部类,实现了迭代器接口
重要的成员
/** Index to look for new nextItem; NONE at end */
private int cursor; // 迭代器内部的游标 相当于takeIndex + 1
/** Element to be returned by next call to next(); null if none */
private E nextItem; // 下一个元素
/** Index of nextItem; NONE if none, REMOVED if removed elsewhere */
private int nextIndex; // 下一个索引 相当于takeIndex
/** Last element returned; null if none or not detached. */
private E lastItem; // 最后一个元素
/** Index of lastItem, NONE if none, REMOVED if removed elsewhere */
private int lastRet;
/** Previous value of takeIndex, or DETACHED when detached */
private int prevTakeIndex; // 相当于takeIndex
/** Previous value of iters.cycles */
private int prevCycles;
// 三个常量
/** Special index value indicating "not available" or "undefined" */
private static final int NONE = -1;
/**
* Special index value indicating "removed elsewhere", that is,
* removed by some operation other than a call to this.remove().
*/
private static final int REMOVED = -2;
/** Special value for prevTakeIndex indicating "detached mode" */
private static final int DETACHED = -3;
初始化
Itr() {
// assert lock.getHoldCount() == 0;
this.lastRet = NONE;
final ReentrantLock lock = ArrayBlockingQueue.this.lock;
lock.lock();
try {
if (count == 0) { // 如果集合为空
// assert itrs == null;
this.cursor = NONE; // 初始化这些成员
this.nextIndex = NONE;
this.prevTakeIndex = DETACHED;
} else {
final int takeIndex = ArrayBlockingQueue.this.takeIndex;
this.prevTakeIndex = takeIndex; // 记录prevTakeIndex 为takeIndex
this.nextItem = itemAt(this.nextIndex = takeIndex); // 记录nextIndex 为takeIndex,并且记录nextItem
this.cursor = incCursor(takeIndex); // 将takeIndex+1并赋值给cursor
if (itrs == null) {
itrs = new Itrs(this);// 如果集合的这个成员为空就进行初始化,这个等到讲Itrs的时候再详细说
} else {
itrs.register(this); // in this order
itrs.doSomeSweeping(false);
}
this.prevCycles = itrs.cycles;
// assert takeIndex >= 0;
// assert prevTakeIndex == takeIndex;
// assert nextIndex >= 0;
// assert nextItem != null;
}
} finally {
lock.unlock();
}
}
private int incCursor(int index) { // 传入的参数是takeIndex
// assert lock.getHoldCount() == 1;
if (++index == items.length)
index = 0; // 如果加1之后超过了索引最大值,就归零
if (index == putIndex)
index = NONE;// 如果等于了putIndex,说明集合为空
return index;
}
重要的方法
public E next() {
// assert lock.getHoldCount() == 0;
final E x = this.nextItem; // 先获得nextItem(初始化的时候已经获取)
if (x == null)
throw new NoSuchElementException();
final ReentrantLock lock = ArrayBlockingQueue.this.lock;
lock.lock();
try {
if (!isDetached())
incorporateDequeues();
// assert nextIndex != NONE;
// assert lastItem == null;
this.lastRet = this.nextIndex; // lastRet就是记录上一次获取元素的索引位置
final int cursor = this.cursor;
if (cursor >= 0) {
this.nextItem = itemAt(this.nextIndex = this.cursor);
// assert nextItem != null;
this.cursor = incCursor(cursor); // 对游标进行+1的操作
} else {
nextIndex = NONE;
nextItem = null;
}
} finally {
lock.unlock();
}
return x;
}
boolean isDetached() {
// assert lock.getHoldCount() == 1;
return prevTakeIndex < 0; // 只要被初始化过,这个prev就不会小于0,应该等于上一次的takeIndex
}
public boolean hasNext() {
// assert lock.getHoldCount() == 0;
if (nextItem != null) // 因为在next()方法中,返回下一个元素后,会再对nextItem进行赋值,所以如果有的话,是不会为空的
return true;
noNext();
return false;
}
还有一个Itrs的内部类
内部类 Itrs
类的定义
class Itrs
但是他内部又封装了一个Node类
/** Incremented whenever takeIndex wraps around to 0 */
int cycles = 0; // 这个变量记录了,takeIndex转了几圈
/** Linked list of weak iterator references */
private Node head;
/** Used to expunge stale iterators */
private Node sweeper = null;
private static final int SHORT_SWEEP_PROBES = 4;
private static final int LONG_SWEEP_PROBES = 16;
private class Node extends WeakReference<Itr> {
Node next;
Node(Itr iterator, Node next) { // 整个iterator就会是前面的内部类Itr 迭代器
super(iterator);
this.next = next; // 指向下一个节点
}
}
void doSomeSweeping(boolean tryHarder) {// 感觉是做一些清理的工作,对内存的释放,变量置空等等
// assert lock.getHoldCount() == 1;
// assert head != null;
int probes = tryHarder ? LONG_SWEEP_PROBES : SHORT_SWEEP_PROBES;
Node o, p;
final Node sweeper = this.sweeper;
boolean passedGo; // to limit search to one full sweep
if (sweeper == null) {
o = null;
p = head;
passedGo = true;
} else {
o = sweeper;
p = o.next;
passedGo = false;
}
for (; probes > 0; probes--) {
if (p == null) {
if (passedGo)
break;
o = null;
p = head;
passedGo = true;
}
final Itr it = p.get();
final Node next = p.next;
if (it == null || it.isDetached()) {
// found a discarded/exhausted iterator
probes = LONG_SWEEP_PROBES; // "try harder"
// unlink p
p.clear();
p.next = null;
if (o == null) {
head = next;
if (next == null) {
// We've run out of iterators to track; retire
itrs = null;
return;
}
}
else
o.next = next;
} else {
o = p;
}
p = next;
}
this.sweeper = (p == null) ? null : o;
}
关于这两个内部类,看了挺久并不能很好的理解,等到时候水平增长,再回顾此博客的时候,可能会有所更新,让我们继续看下一个阻塞容器吧。Orz
二、LinkedBlockingQueue
1.类的定义
// 和ArrayBlockingQueue 定义是一样的,所以它们是兄弟类,但是它的实现使用了链表结构,而非数组
public class LinkedBlockingQueue<E> extends AbstractQueue<E>
implements BlockingQueue<E>, java.io.Serializable
2.重要的成员变量
/** The capacity bound, or Integer.MAX_VALUE if none */
private final int capacity; // 集合的容量,LinkedBlockingQueue 默认的容量是 Integer.MAX_VALUE
/** Current number of elements */
private final AtomicInteger count = new AtomicInteger(); // 这里为什么没有直接用int,我想因为LinkedBlockingQueue用了两把锁的机制,来提高入队和出队并发时候的效率,所以这里的count为了取到最准确的数据用了原子类
/**
* Head of linked list.
* Invariant: head.item == null
*/
transient Node<E> head; // 链表结构的头结点,这个Node是个傀儡节点,它的item 永远都等于null,它的作用就是指向队列中的第一个节点
/**
* Tail of linked list.
* Invariant: last.next == null
*/
private transient Node<E> last;// 链表结构的尾节点,初始化的时候,和head指向同一个Node,但是添加元素后,尾结点代表的就是整个集合最后一个被添加的元素
/** Lock held by take, poll, etc */
private final ReentrantLock takeLock = new ReentrantLock();// 出队锁
/** Wait queue for waiting takes */
private final Condition notEmpty = takeLock.newCondition();// 同ArrayBlockingQueue
/** Lock held by put, offer, etc */
private final ReentrantLock putLock = new ReentrantLock();// 入队锁
/** Wait queue for waiting puts */
private final Condition notFull = putLock.newCondition();
看到这里,可以知道,LBQ用的是两把锁,所以在处理入队出队都高并发的场景的时候,效率比ABQ要高许多,这也是为什么java内置的线程池使用LBQ作为默认的阻塞队列实现了。
这里看到了一个新的对象Node,让我们来看看吧。
static class Node<E> {// 就是LBQ的静态内部类
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; }// 构造器
}
3.初始化
/**
* Creates a {@code LinkedBlockingQueue} with a capacity of
* {@link Integer#MAX_VALUE}.
*/
public LinkedBlockingQueue() {
this(Integer.MAX_VALUE); // 默认 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;
last = head = new Node<E>(null);// 并在这里初始化 头尾节点
}
/**
* Creates a {@code LinkedBlockingQueue} with a capacity of
* {@link Integer#MAX_VALUE}, initially containing the elements of the
* given collection,
* added in traversal order of the collection's iterator.
*
* @param c the collection of elements to initially contain
* @throws NullPointerException if the specified collection or any
* of its elements are null
*/
public LinkedBlockingQueue(Collection<? extends E> c) {
this(Integer.MAX_VALUE);
final ReentrantLock putLock = this.putLock;
// 和ABQ不一样的是,入队操作只需要使用入队锁就行了
putLock.lock(); // Never contended, but necessary for visibility
try {
int n = 0;
for (E e : c) {
if (e == null)
throw new NullPointerException();
if (n == capacity)
throw new IllegalStateException("Queue full");
enqueue(new Node<E>(e));// 看名字就知道是入队的操作
++n;
}
count.set(n);// 用了原子类去更新原子数目
} finally {
putLock.unlock();
}
}
/**
* 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;// 先把当前尾结点的next指向新的节点,最后把新节点设置为新的尾结点
}
4.一些重要的非公开的方法
/**
* Removes a node from head of queue.
* 出队操作(删除第一个节点),当初我看到这个方法的时候有个疑问,为什么不直接把head节点的next指向,first节点的next,然后再操作first节点指向自己,可能大神有自己的想法吧
* @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; // 自己指向自己,用于GC回收
head = first; //把原来第一个节点设置成头结点
E x = first.item; // 获取原先第一个节点的元素,用于返回
first.item = null; // 因为前面说了head节点的item永远都为空,所以这里置空
return x;
}
// 锁相关
/**
* Locks to prevent both puts and takes.
*/
void fullyLock() {
putLock.lock();
takeLock.lock();
}
/**
* Unlocks to allow both puts and takes.
*/
void fullyUnlock() {
takeLock.unlock();
putLock.unlock();
}
// 线程通信相关
/**
* 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();
}
}
/**
* Signals a waiting put. Called only from take/poll.
*/
private void signalNotFull() {
final ReentrantLock putLock = this.putLock;
putLock.lock();
try {
notFull.signal();
} finally {
putLock.unlock();
}
}
5.入队和出队操作(重要)
1.入队操作(offer, put)
可以看到LBQ是没有add方法的,我猜原因可能是因为LBQ可以说是无限容量的Integer最大值,所以达到上限的时候直接offer返回false就行了
/**
* Inserts the specified element at the tail of this queue if it is
* possible to do so immediately without exceeding the queue's capacity,
* returning {@code true} upon success and {@code false} if this queue
* is full.
* When using a capacity-restricted queue, this method is generally
* preferable to method {@link BlockingQueue#add add}, which can fail to
* insert an element only by throwing an exception.
*
* @throws NullPointerException if the specified element is null
*/
public boolean offer(E e) {
if (e == null) throw new NullPointerException(); // 和ABQ一样,元素都不能为空
final AtomicInteger count = this.count;
if (count.get() == capacity) // 达到存储上限了,直接返回false
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(); // 原子类的方式 +1,这个c返回的是增加前的容量
if (c + 1 < capacity) // 理论上这里的c+1和自增后的count是一样的
notFull.signal(); // 仍有剩余的容量,则通知生产者
}
} finally {
putLock.unlock();
}
if (c == 0) // 这里我其实看不太懂,c初始是-1,之后入队成功后,c就成了入队之前的count值,等于0只能说明,本次入队前集合为空。所以这里的singnalNotEmpty通知消费者,不应该条件是 c >= 0 么?
signalNotEmpty();
return c >= 0; // c只要大于-1,说明就入队成功,不然就失败
}
/**
* Inserts the specified element at the tail of this queue, waiting if
* necessary up to the specified wait time for space to become available.
*
* @return {@code true} if successful, or {@code false} if
* the specified waiting time elapses before space is available
* @throws InterruptedException {@inheritDoc}
* @throws NullPointerException {@inheritDoc}
*/
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(); // 由于本方法有超时时间的参数,所以用了的可中断的方式获取lock
try {
while (count.get() == capacity) { // 用死循环判断,只要有出队操作后,while就返回false
if (nanos <= 0) // 一旦时间到了就返回
return false;
nanos = notFull.awaitNanos(nanos); // 每次都更新nanos时间
}
enqueue(new Node<E>(e)); // 入队操作
c = count.getAndIncrement(); // 原子类的方式自加1
if (c + 1 < capacity)
notFull.signal();
} finally {
putLock.unlock();
}
if (c == 0) // 同上
signalNotEmpty();
return true;
}
/**
* 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(); // put方法和ABQ一样,会一直阻塞到可以入队位置
}
// 下面和offer是一样的,只是没有返回值
enqueue(node);
c = count.getAndIncrement();
if (c + 1 < capacity)
notFull.signal();
} finally {
putLock.unlock();
}
if (c == 0)
signalNotEmpty();
}
方法名 | 备注 |
---|---|
offer | 添加成功返回true,否则返回false (常用,推荐) |
put | 当队列已满的时候,线程阻塞,等待消费者消费通知,直到入队成功 |
2.出队操作(poll, peek, take, remove)
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) { // 判断条件改成了count大于0
x = dequeue(); // 出队
c = count.getAndDecrement(); // 原子类 自减1,c同样还是自减1之前的值
if (c > 1) // 仍然有元素可以消费
notEmpty.signal();
}
} finally {
takeLock.unlock();
}
if (c == capacity)
signalNotFull(); // 这里改成了通知生产者,但是条件上的疑问和offer是一样的
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) // 基本同offer,只是改成了出队
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;
}
// 和ABQ是一样的, 只返回队首的元素,并不移除
public E peek() {
if (count.get() == 0)
return null;
final ReentrantLock takeLock = this.takeLock;
takeLock.lock();
try {
Node<E> first = head.next;
if (first == null)
return null;
else
return first.item;
} finally {
takeLock.unlock();
}
}
// 基本同put,只是改成了出队和自减1
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;
}
/**
* Removes a single instance of the specified element from this queue,
* if it is present. More formally, removes an element {@code e} such
* that {@code o.equals(e)}, if this queue contains one or more such
* elements.
* Returns {@code true} if this queue contained the specified element
* (or equivalently, if this queue changed as a result of the call).
*
* @param o element to be removed from this queue, if present
* @return {@code true} if this queue changed as a result of the call
*/
public boolean remove(Object o) { // remove删除指定的元素,取决于对象的equals方法
if (o == null) return false;
fullyLock(); // 这里用的是全锁
try {
for (Node<E> trail = head, p = trail.next;
p != null;
trail = p, p = p.next) {
if (o.equals(p.item)) {
unlink(p, trail); // 重要的方法,用来删除两个节点之间的关联,删除的是p节点
return true;
}
}
return false;
} finally {
fullyUnlock();
}
}
/**
* Unlinks interior Node p with predecessor trail.
*/
void unlink(Node<E> p, Node<E> trail) { // p是当前要删除的节点,trail的next是p节点
// assert isFullyLocked();
// p.next is not changed, to allow iterators that are
// traversing p to maintain their weak-consistency guarantee.
p.item = null; // 由于要删除p节点,所以先将它置空
trail.next = p.next; // 将trail的next设置成p的next
if (last == p) // 如果p就是最后一个节点
last = trail; // 那把trail设置成last
if (count.getAndDecrement() == capacity) // 自减1之前是满容量
notFull.signal(); // 自减1之后,就通知生产者可以继续生产
}
同ABQ
方法名 | 备注 |
---|---|
poll | 移除成功返回true,否则返回false (常用,推荐) |
peek | 只返回队列最前面的元素,并不移除它 |
take | 当队列空的时候,线程阻塞,等待生产者生产通知,直到出队成功 |
remove | 有入参,可以移除指定的元素,但是会对集合进行遍历,效率低(移除是否成功和重写的equals相关) |
6.其他方法
/**
* Returns the number of elements in this queue.
* 返回集合中元素的个数
* @return the number of elements in this queue
*/
public int size() {
return count.get();
}
/**
* Returns the number of elements in this queue.
* 返回 剩余的容量
* @return the number of elements in this queue
*/
public int size() {
return count.get();
}
/**
* Returns an array containing all of the elements in this queue, in
* proper sequence.
*
* <p>The returned array will be "safe" in that no references to it are
* maintained by this queue. (In other words, this method must allocate
* a new array). The caller is thus free to modify the returned array.
*
* <p>This method acts as bridge between array-based and collection-based
* APIs.
*
* @return an array containing all of the elements in this queue
*/
public Object[] toArray() { // 和ABQ不一样的是,由于底层实现不同,LBQ只能遍历完整个链表进行数组的赋值,ABQ用的是数组复制的底层本地方法,所以效率上会高一点
fullyLock();
try {
int size = count.get();
Object[] a = new Object[size];
int k = 0;
for (Node<E> p = head.next; p != null; p = p.next)
a[k++] = p.item;
return a;
} finally {
fullyUnlock();
}
}
public <T> T[] toArray(T[] a) {
fullyLock();
try {
int size = count.get();
if (a.length < size)
a = (T[])java.lang.reflect.Array.newInstance
(a.getClass().getComponentType(), size);
int k = 0;
for (Node<E> p = head.next; p != null; p = p.next)
a[k++] = (T)p.item;
if (a.length > k)
a[k] = null;
return a;
} finally {
fullyUnlock();
}
}
public String toString() {
fullyLock();
try {
Node<E> p = head.next;
if (p == null)
return "[]";
StringBuilder sb = new StringBuilder();
sb.append('[');
for (;;) {
E e = p.item;
sb.append(e == this ? "(this Collection)" : e);
p = p.next;
if (p == null)
return sb.append(']').toString();
sb.append(',').append(' ');
}
} finally {
fullyUnlock();
}
}
public void clear() {
fullyLock();
try {
for (Node<E> p, h = head; (p = h.next) != null; h = p) {
h.next = h;
p.item = null;
}
head = last;
// assert head.item == null && head.next == null;
if (count.getAndSet(0) == capacity)
notFull.signal();
} finally {
fullyUnlock();
}
}
/**
* @throws UnsupportedOperationException {@inheritDoc}
* @throws ClassCastException {@inheritDoc}
* @throws NullPointerException {@inheritDoc}
* @throws IllegalArgumentException {@inheritDoc}
*/
public int drainTo(Collection<? super E> c) {
return drainTo(c, Integer.MAX_VALUE);
}
/**
* @throws UnsupportedOperationException {@inheritDoc}
* @throws ClassCastException {@inheritDoc}
* @throws NullPointerException {@inheritDoc}
* @throws IllegalArgumentException {@inheritDoc}
*/
public int drainTo(Collection<? super E> c, int maxElements) {
if (c == null)
throw new NullPointerException();
if (c == this)
throw new IllegalArgumentException();
if (maxElements <= 0)
return 0;
boolean signalNotFull = false;
final ReentrantLock takeLock = this.takeLock;
takeLock.lock();
try {
int n = Math.min(maxElements, count.get());
// count.get provides visibility to first n Nodes
Node<E> h = head;
int i = 0;
try {
while (i < n) {
Node<E> p = h.next;
c.add(p.item);
p.item = null;
h.next = h;
h = p;
++i;
}
return n;
} finally {
// Restore invariants even if c.add() threw
if (i > 0) {
// assert h.item == null;
head = h;
signalNotFull = (count.getAndAdd(-i) == capacity);
}
}
} finally {
takeLock.unlock();
if (signalNotFull)
signalNotFull();
}
}
/**
* Returns an iterator over the elements in this queue in proper sequence.
* The elements will be returned in order from first (head) to last (tail).
*
* <p>The returned iterator is
* <a href="package-summary.html#Weakly"><i>weakly consistent</i></a>.
*
* @return an iterator over the elements in this queue in proper sequence
*/
public Iterator<E> iterator() {
return new Itr();
}
内部类(迭代器)Itr
类的定义
private class Itr implements Iterator<E>
重要的成员
// 可以看到内部迭代器的实现还是使用的是刚刚内部类Node
private Node<E> current; // 当前的节点(next()方法直接返回的节点)
private Node<E> lastRet; // 上一个节点
private E currentElement; // current节点的item
初始化
Itr() {
fullyLock();
try {
current = head.next; // 初始化被赋值成 head的next节点(即集合中的第一个节点)
if (current != null)
currentElement = current.item;
} finally {
fullyUnlock();
}
}
重要的方法
public boolean hasNext() {
return current != null;
}
/**
* Returns the next live successor of p, or null if no such.
*
* Unlike other traversal methods, iterators need to handle both:
* - dequeued nodes (p.next == p)
* - (possibly multiple) interior removed nodes (p.item == null)
*/
private Node<E> nextNode(Node<E> p) {
for (;;) {
Node<E> s = p.next;
if (s == p)
return head.next;
if (s == null || s.item != null)
return s;
p = s;
}
}
public E next() {
fullyLock();
try {
if (current == null)
throw new NoSuchElementException();
E x = currentElement;
lastRet = current;
current = nextNode(current);
currentElement = (current == null) ? null : current.item;
return x;
} finally {
fullyUnlock();
}
}
public void remove() {
if (lastRet == null)
throw new IllegalStateException();
fullyLock();
try {
Node<E> node = lastRet;
lastRet = null;
for (Node<E> trail = head, p = trail.next;
p != null;
trail = p, p = p.next) {
if (p == node) {
unlink(p, trail);
break;
}
}
} finally {
fullyUnlock();
}
}