集合类源码(四)Collection之BlockingQueue(ArrayBlockingQueue, DelayQueue, LinkedBlockingQueue)
ArrayBlockingQueue
功能
全名
public class ArrayBlockingQueue<E>
extends AbstractQueue<E>
implements BlockingQueue<E>, Serializable
简述
由数组支持的有界阻塞队列。这个队列对元素FIFO(先进先出)排序。队列的头是队列中存在时间最长的元素。队列的尾部是队列中存在时间最短的元素。新元素插入到队列的尾部,队列检索操作获取队列头部的元素。
这是一个典型的“有界缓冲区”,其中大小固定的数组保存由生产者插入并由消费者提取的元素。一旦创建,容量就不能更改。试图将一个元素放入一个满队列将导致操作阻塞;尝试从空队列中获取元素也会发生阻塞。
该类支持一个可选的公平性策略,用于对正在等待的生产者和消费者线程进行排序。默认情况下,不保证这种顺序。但是,将公平性设置为true的队列将按FIFO顺序授予线程访问权。公平性通常会降低吞吐量,但会降低可变性并避免饥饿。
方法
// 在不超过队列容量的情况下立即插入指定的元素,成功后返回true,如果队列已满则抛出IllegalStateException。
public boolean add(E e) // 在不超过队列容量的情况下立即在队列末尾插入指定的元素,如果成功则返回true,如果队列已满则返回false。此方法通常比add(E)方法更好,后者插入元素失败只能抛出异常。
public boolean offer(E e) // 将指定的元素插入到此队列的末尾,如果队列已满则等待直到有可用的空间。
public void put(E e) throws InterruptedException // 将指定的元素插入到此队列的末尾,如果队列已满,则在指定的超时时间之内等待空间可用,超时返回false。
public boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException // 检索并删除此队列的头,如果此队列为空,则返回null。
public E poll() // 检索并删除此队列的头,如有必要则等待,直到某个元素可用为止。
public E take() throws InterruptedException // 检索并删除此队列的头,如果有必要则在指定的等待时间之内等待元素可用,超时返回null。
public E poll(long timeout, TimeUnit unit) throws InterruptedException // 检索但不删除此队列的头,或在此队列为空时返回null。
public E peek() // 返回此队列中的元素数量。
public int size() // 返回此队列在理想情况下(在没有内存或资源约束的情况下)可以不阻塞地接受的新元素的数量。它总是等于这个队列的初始容量减去这个队列的当前大小。
public int remainingCapacity() // 如果指定元素存在,则从此队列中移除该元素的单个实例。更正式地说,如果队列中包含一个或多个这样的元素,则只删除匹配到的第一个元素
public boolean remove(Object o) // 如果此队列包含至少一个指定的元素,则返回true。
public boolean contains(Object o) // 返回一个数组,该数组包含此队列中的所有元素,按适当的顺序排列。返回的数组将是“安全的”,因为此队列不维护对它的引用。
public Object[] toArray() // 返回一个数组,该数组包含此队列中的所有元素,按适当的顺序排列;返回数组的运行时类型是指定数组的运行时类型。
public <T> T[] toArray(T[] a) // 返回此集合的字符串表示形式。
public String toString() // 删除此队列中的所有元素。此调用返回后,队列将为空。
public void clear() // 从此队列中删除所有可用元素并将它们添加到给定集合中。此操作可能比重复轮询此队列更有效。在试图将元素添加到集合c时遇到失败抛出相关异常时可能会导致:元素不在原集合或者集合c中,或者两个集合中都没有。
public int drainTo(Collection<? super E> c) // 从该队列中最多删除给定数量的可用元素,并将它们添加到给定集合中。异常情况同上
public int drainTo(Collection<? super E> c, int maxElements) // 按适当的顺序返回此队列中元素的迭代器。元素将按从第一个(head)到最后一个(tail)的顺序返回。返回的迭代器是弱一致的。
public Iterator<E> iterator() // 返回该队列中元素的Spliterator。返回的spliterator是弱一致的。
public Spliterator<E> spliterator()
原理
/** 队列里元素数量 */
int count;
/** 存储结构 */
final Object[] items; /** 为下一次执行 take, poll, peek or remove 操作提供的index */
int takeIndex; /** 为下一次执行 put, offer, or add 操作提供的index */
int putIndex; /** 当队列为空时获取等待 */
private final Condition notEmpty; /** 当队列满时插入等待 */
private final Condition notFull;
offer和put
public boolean offer(E e) {
// 不允许null值
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lock();
try {
// 队列里元素的数量等于队列长度(队列满,插入返回false)
if (count == items.length)
return false;
else {
// 插入
enqueue(e);
return true;
}
} finally {
lock.unlock();
}
} private void enqueue(E x) {
// assert lock.getHoldCount() == 1;
// assert items[putIndex] == null;
final Object[] items = this.items;
// 插入数据
items[putIndex] = x;
// 如果putIndex+1等于队列长度,将putIndex置为0,为下一轮插入做准备
if (++putIndex == items.length)
putIndex = 0;
// 队列元素数量+1
count++;
// 通知读取线程可以读取了
notEmpty.signal();
} public void put(E e) throws InterruptedException {
// null值检查
checkNotNull(e);
final ReentrantLock lock = this.lock;
// 获取一个优先考虑中断的锁
lock.lockInterruptibly();
try {
// 只要队列满了,则插入线程阻塞,直到有可用空间
while (count == items.length)
notFull.await();
enqueue(e);
} finally {
lock.unlock();
}
} public boolean offer(E e, long timeout, TimeUnit unit)
throws InterruptedException {
// null值检查
checkNotNull(e);
long nanos = unit.toNanos(timeout);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
// 在队列满了之后进入循环
while (count == items.length) {
// 如果小于等于0则超时,返回false
if (nanos <= 0)
return false;
// awaitNanos方法:使当前线程等待,直到收到signal或被中断,或指定的等待时间过期。
// 如果在时间之内收到了signal,则返回timeout - 已等待的时间;如果超时了,则返回0或者负数
nanos = notFull.awaitNanos(nanos);
}
enqueue(e);
return true;
} finally {
lock.unlock();
}
}
poll和take
public E poll() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
// 如果队列空了,直接返回null
return (count == 0) ? null : dequeue();
} finally {
lock.unlock();
}
} 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; // 当前位置置为空
// 如果takeIndex+1等于队列长度,takeIndex置为0,为下一轮读取做准备
if (++takeIndex == items.length)
takeIndex = 0;
// 队列元素数量-1
count--;
// 如果当前迭代器不为空,迭代器也要做出更新
if (itrs != null)
itrs.elementDequeued();
// 通知写入线程写入数据
notFull.signal();
return x;
} public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
// 如果队列为空则一直等待,直到有可用元素
while (count == 0)
notEmpty.await();
return dequeue();
} finally {
lock.unlock();
}
} 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) {
// 如果小于等于0则超时,返回null
if (nanos <= 0)
return null;
// awaitNanos方法:使当前线程等待,直到收到signal或被中断,或指定的等待时间过期。
// 如果在时间之内收到了signal,则返回timeout - 已等待的时间;如果超时了,则返回0或者负数
nanos = notEmpty.awaitNanos(nanos);
}
return dequeue();
} finally {
lock.unlock();
}
}
从上面的进队出队我们可以知道大概的流程,首先内部存储结构是一个定长数组,初始情况putIndex和takeIndex都为0
当执行插入后,putIndex+1,为下一次插入做准备。当putIndex移动到最后的时候,+1正好等于队列最大长度,这个时候要将它置为初始状态也就是0。takeIndex同样的道理。
那么为什么要这么做,我已经知道了:避免了插入删除导致的元素移动!同时也保证了队列的性质:先进先出。
remove方法
public boolean remove(Object o) {
if (o == null) return false;
final Object[] items = this.items;
final ReentrantLock lock = this.lock;
lock.lock();
try {
if (count > 0) {
// final变量,不可变,下面循环做条件
final int putIndex = this.putIndex;
// 初始从takeIndex所指的位置开始
int i = takeIndex;
do {
// 找到了,执行移除代码
if (o.equals(items[i])) {
removeAt(i);
return true;
}
// 到末尾了,从头开始找
if (++i == items.length)
i = 0;
} while (i != putIndex); // 只要不是putIndex所指,继续找(因为putIndex所指的位置是null)
}
return false;
} finally {
lock.unlock();
}
} void removeAt(final int removeIndex) {
// assert lock.getHoldCount() == 1;
// assert items[removeIndex] != null;
// assert removeIndex >= 0 && removeIndex < items.length;
final Object[] items = this.items;
// 如果移除的位置和takeIndex指向一致,相当于执行了一次出队操作,仅仅改变takeIndex即可。
if (removeIndex == takeIndex) {
// 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 {
// 直到当前位置的下一个是putIndex,将当前位置置null,putIndex指向当前位置,跳出循环
items[i] = null;
this.putIndex = i;
break;
}
}
// 元素数量-1
count--;
// 同步迭代器
if (itrs != null)
itrs.removedAt(removeIndex);
}
notFull.signal();
}
remove方法,移除元素的位置不是takeIndex的时候,移除过程是下面这样
最后看一下drainTo
// 把队列元素删除,并将元素添加到集合c中
public int drainTo(Collection<? super E> c) {
// 这里可以看到,默认删除数量为Integer.MAX_VALUE
return drainTo(c, Integer.MAX_VALUE);
}
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);
// 从takeIndex开始
int take = takeIndex;
int i = 0;
try {
// 操作n个元素
while (i < n) {
@SuppressWarnings("unchecked")
E x = (E) items[take];
// 添加到c中
c.add(x);
// 原队列删除
items[take] = null;
// take向后移动;如果到了队尾,从头开始
if (++take == items.length)
take = 0;
i++;
}
return n;
} finally {
// Restore invariants even if c.add() threw
if (i > 0) {
count -= i; // 剩余队列元素数量
takeIndex = take; // 重置takeIndex
if (itrs != null) {
// 如果队列空了,迭代器也要做出同步
if (count == 0)
itrs.queueIsEmpty();
else if (i > take)
// 当takeIndex变为0时调用。
itrs.takeIndexWrapped();
}
// 通知等待的插入线程
for (; i > 0 && lock.hasWaiters(notFull); i--)
notFull.signal();
}
}
} finally {
lock.unlock();
}
}
优缺点
优点:有界队列,避免了内存滥用;内部插入删除实现避免了元素移动,时间复杂度为O(1),高效率;线程安全;
缺点:我觉得这个在特定场景已经很完美了。
如果你想在进队出队时不满足条件立即返回,则直接用offer和poll;如果你希望等待,则用put和take;如果你希望等一会,不行再返回,则用offer(E e, long timeout, TimeUnit unit)和poll(long timeout, TimeUnit unit)。
remove方法尽量不用吧,因为它在删除之后要去判断调整putIndex或者takeIndex。
DelayQueue
功能
全名
public class DelayQueue<E extends Delayed>
extends AbstractQueue<E>
implements BlockingQueue<E>
简述
延迟元素的无界阻塞队列,其中一个元素只能在其延迟过期后才能被获取。
队列的头是延迟元素。如果没有过期,就没有head, poll将返回null。
当元素的getDelay(TimeUnit.NANOSECONDS)方法返回一个小于或等于零的值时,就会发生过期。
未过期的元素不能使用take或poll删除,它们被视为正常元素。根据定义,它的元素必须是Delayed接口的实现。
方法
// 将指定的元素插入此延迟队列。
public boolean add(E e) // 将指定的元素插入此延迟队列
public boolean offer(E e) // 将指定的元素插入此延迟队列。因为队列是无界的,所以这个方法永远不会阻塞。
public void put(E e) // 将指定的元素插入此延迟队列。因为队列是无界的,所以这个方法永远不会阻塞。
public boolean offer(E e, long timeout, TimeUnit unit) // 检索并删除此队列的头,如果此队列没有过期的元素,则返回null。
public E poll() // 检索并删除此队列的头,如有必要,将一直等待,直到此队列上有一个过期的元素可用为止。
public E take() throws InterruptedException // 检索并删除此队列的头,如有必要,将一直等待,直到此队列中具有过期延迟的元素可用,或指定的等待时间过期。
public E poll(long timeout,
TimeUnit unit)
throws InterruptedException // 检索但不删除此队列的头,或在此队列为空时返回null。与poll方法不同,如果队列中没有可用的过期元素,此方法将返回下一个将要过期的元素(如果存在的话)。
public E peek() // 返回此集合中的元素数。如果此集合元素数大于Integer.MAX_VALUE,也只返回Integer.MAX_VALUE
public int size() // 从此队列中删除所有可用元素并将它们添加到给定集合中。此操作可能比重复轮询此队列更有效。在试图将元素添加到集合c时遇到失败抛出相关异常时可能会导致:元素不在原集合或者集合c中,或者两个集合中都没有。
public int drainTo(Collection<? super E> c) // 从该队列中最多删除给定数量的可用元素,并将它们添加到给定集合中。异常情况同上
public int drainTo(Collection<? super E> c, int maxElements) // 删除此延迟队列中的所有元素。此调用返回后,队列将为空。不等待未过期的元素;它们只是从队列中被丢弃。
public void clear() // 总是返回Integer.MAX_VALUE,因为延迟队列没有容量限制。
public int remainingCapacity() // 返回一个数组,该数组包含此队列中的所有元素,按适当的顺序排列。返回的数组将是“安全的”,因为此队列不维护对它的引用。
public Object[] toArray() // 返回一个数组,该数组包含此队列中的所有元素,按适当的顺序排列;返回数组的运行时类型是指定数组的运行时类型。
public <T> T[] toArray(T[] a) // 从此队列中删除指定元素的单个实例(如果存在),无论它是否已过期。
public boolean remove(Object o) // 返回此队列中所有元素(过期和未过期)的迭代器。迭代器不会以任何特定的顺序返回元素。返回的迭代器是弱一致的。
public Iterator<E> iterator()
原理
成员变量
private final transient ReentrantLock lock = new ReentrantLock();
// 延迟队列的内部存储结构是优先级队列 [praɪˈɒrəti][kjuː]
private final PriorityQueue<E> q = new PriorityQueue<E>(); private Thread leader = null;
// 当一个新的元素在队列的最前面可用,或者一个新线程需要成为leader时,就会发出条件信号。
private final Condition available = lock.newCondition();
offer和put
public boolean offer(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
// 入队
q.offer(e);
if (q.peek() == e) {
leader = null;
// 如果队头元素是当前元素(新添加的元素已经过期),则通知等待线程
available.signal();
}
return true;
} finally {
lock.unlock();
}
} // 实际调用的是offer
public void put(E e) {
offer(e);
}
// 不仅仅调用的offer,并且timeout参数,unit参数都没有用到
public boolean offer(E e, long timeout, TimeUnit unit) {
return offer(e);
} // java.util.PriorityQueue#offer
public boolean offer(E e) {
if (e == null)
throw new NullPointerException();
// 内部存储结构修改次数
modCount++;
int i = size;
// 如果队列元素数量大于等于队列长度,则扩容
if (i >= queue.length)
grow(i + 1);
// 队列元素数量+1
size = i + 1;
if (i == 0)
// 如果队列为空,直接放在第一位
queue[0] = e;
else
// 队列不为空,在i的位置插入e。【这里面有个排序的过程,根据我们定义的排序规则把最早过期的排在内部数组第一位,保证队头是最早过期的元素】
siftUp(i, e);
return true;
}
// java.util.PriorityQueue#grow
private void grow(int minCapacity) {
int oldCapacity = queue.length;
// 如果老容量小于64,则新容量 = 2*老容量+2;如果老容量大于等于64,则新容量 = 1.5*老容量
int newCapacity = oldCapacity + ((oldCapacity < 64) ?
(oldCapacity + 2) :
(oldCapacity >> 1));
// overflow-conscious code
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
queue = Arrays.copyOf(queue, newCapacity);
}
// java.util.PriorityQueue#peek
public E peek() {
// 不为空,则返回队列第一个元素(准确来说是内部数组第一个)
return (size == 0) ? null : (E) queue[0];
}
这里可以看出,延迟队列的内部实现是优先级队列,优先级队列的内部实现是一个数组,扩容的时候新容量与ArrayList的有一丢丢不一样。因为可以扩容,所以延时队列是个无界队列。
poll和take
public E poll() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
// 获取队头
E first = q.peek();
// 如果队头为空或者未过期,直接返回空
if (first == null || first.getDelay(NANOSECONDS) > 0)
return null;
else
// 否则出队并返回
return q.poll();
} finally {
lock.unlock();
}
} public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
for (;;) {
E first = q.peek();
// 如果获取不到,则等待
if (first == null)
available.await();
else {
// 获取剩余过期时间
long delay = first.getDelay(NANOSECONDS);
// 满足过期条件,出队并返回
if (delay <= 0)
return q.poll();
// 当线程等待,不保留这个引用
first = null; // don't retain ref while waiting
if (leader != null)
available.await();
else {
Thread thisThread = Thread.currentThread();
leader = thisThread;
try {
// 等待剩余过期时间那么长的时间
available.awaitNanos(delay);
} finally {
if (leader == thisThread)
leader = null;
}
}
}
}
} finally {
if (leader == null && q.peek() != null)
available.signal();
lock.unlock();
}
} public E poll(long timeout, TimeUnit unit) throws InterruptedException {
long nanos = unit.toNanos(timeout);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
for (;;) {
E first = q.peek();
// 没有获取到有效元素
if (first == null) {
// timeout无效,返回空
if (nanos <= 0)
return null;
else
// 否则等待timeout那么长的时间
nanos = available.awaitNanos(nanos);
} else {
// 获取剩余过期时间
long delay = first.getDelay(NANOSECONDS);
if (delay <= 0)
// 已经过期,顺利出队返回
return q.poll();
// timeout无效,返回空
if (nanos <= 0)
return null;
first = null; // don't retain ref while waiting
if (nanos < delay || leader != null)
// 如果timeout时间小于剩余过期时间,等待timeout那么长的时间
nanos = available.awaitNanos(nanos);
else {
Thread thisThread = Thread.currentThread();
leader = thisThread;
try {
// 否则等待剩余过期时间那么长的时间
long timeLeft = available.awaitNanos(delay);
nanos -= delay - timeLeft;
} finally {
if (leader == thisThread)
leader = null;
}
}
}
}
} finally {
if (leader == null && q.peek() != null)
available.signal();
lock.unlock();
}
}
小例子:
import java.util.concurrent.*; public class Main {
public static void main(String[] args) throws Exception {
DelayQueue<MyDelay> list = new DelayQueue<>();
list.offer(new MyDelay(1000, "A"));
list.offer(new MyDelay(2000, "B"));
list.offer(new MyDelay(3000, "C"));
for (int i = 0; i < 3; i++){
System.out.println(list.take().toString());
}
}
} class MyDelay implements Delayed { private long expireTime;
private String name; public MyDelay(long expireTime, String name) {
this.expireTime = System.currentTimeMillis() + expireTime;
this.name = name;
} @Override
public long getDelay(TimeUnit unit) {
return expireTime - System.currentTimeMillis();
} @Override
public int compareTo(Delayed o) {
return Long.compare(expireTime, ((MyDelay)o).expireTime);
} @Override
public String toString() {
return "MyDelay{" +
"expireTime=" + expireTime +
", name='" + name + '\'' +
'}';
}
}
运行结果:按照过期顺序输出
优缺点
内部是依赖优先级队列实现的,也可以说,延时队列是优先级队列的一个特例(按照时间过期顺序排序)。因为是无界队列,插入不会阻塞;由于过期时间的限制,poll和take会阻塞。
使用场景:
1. 订单超30分钟未付款即关闭订单。
2. 定时任务:延时队列保存要执行的任务,一旦获取到即执行。
3. 服务器中的客户端连接空闲一段时间之后就需要关闭。
为什么用Leader-Follower模式?
官方说法:Leader为等待队列头部的元素的线程。 Leader-Follower模式的这种变体可以最小化不必要的等待时间。在从take()或poll(…)返回之前,领导线程必须向其他线程发出信号,除非其他线程在此期间成为领导线程。
模式原理:一开始创建一个线程池,选取一个当做Leader监听任务,其它线程等待;当Leader拿到了任务,即释放自己的权利,然后从等待线程中选取一个作为新的Leader去监听任务,自己则去执行拿到的任务,执行完任务进入等待状态。由于接受任务和执行都是同一个线程,则避免了上下文切换的开销。
LinkedBlockingQueue
功能
全名
public class LinkedBlockingQueue<E>
extends AbstractQueue<E>
implements BlockingQueue<E>, Serializable
简述
基于链表的有界阻塞队列。这个队列对元素FIFO(先进先出)排序。队列的头是队列中存在时间最长的元素。队列的尾部是队列中时间最短的元素。
新元素插入到队列的尾部,队列检索操作获取队列头部的元素。链表队列通常比基于数组的队列具有更高的吞吐量,但在大多数并发应用程序中,其性能的可预测性较差。
构造函数里的容量参数用作防止队列过度扩展的一种方法。容量(如果未指定)等于Integer.MAX_VALUE。这里可以得知,LinkedBlockingQueue的有界和无界是灵活的:如果你需要有界,则必须在构造方法指定容量;如果不指定,容量等于Integer.MAX_VALUE,和无界没有什么区别了。
方法
// 返回此队列中的元素数量。
public int size() // 返回此队列在理想情况下(在没有内存或资源约束的情况下)可以不阻塞地接受新元素的数量。它总是等于这个队列的初始容量减去这个队列的当前大小。
public int remainingCapacity() // 将指定的元素插入到此队列的末尾,如果需要,则等待空间可用。
public void put(E e) throws InterruptedException // 将指定的元素插入到此队列的末尾,如有必要,将等待指定的等待时间,直到空间可用为止。超时则返回false。
public boolean offer(E e,
long timeout,
TimeUnit unit)
throws InterruptedException // 在不超过队列容量的情况下立即在队列末尾插入指定的元素,如果成功则返回true,如果队列已满则返回false。当使用容量受限的队列时,此方法通常比add方法更好,后者插入失败仅抛出异常。
public boolean offer(E e) // 检索并删除此队列的头,如有必要则等待,直到某个元素可用为止。
public E take() throws InterruptedException // 检索并删除此队列的头,如有必要,将等待指定的等待时间,直到元素可用。超时返回null。
public E poll(long timeout, TimeUnit unit) throws InterruptedException // 检索并删除此队列的头,如果此队列为空,则返回null。
public E poll() // 检索并删除此队列的头,如果此队列为空,则返回null。
public E peek() // 如果指定元素存在,则从此队列中移除该元素的单个实例。更正式地说,如果队列中包含一个或多个这样的元素,则只删除第一个匹配到的元素。
public boolean remove(Object o) // 如果此队列包含至少一个指定的元素,则返回true。
public boolean contains(Object o) // 返回一个数组,该数组包含此队列中的所有元素,按适当的顺序排列。返回的数组将是“安全的”,因为此队列不维护对它的引用。
public Object[] toArray() // 返回一个数组,该数组包含此队列中的所有元素,按适当的顺序排列;返回数组的运行时类型是指定数组的运行时类型。
public <T> T[] toArray(T[] a) // 返回此集合的字符串表示形式。
public String toString() // 删除此队列中的所有元素。此调用返回后,队列将为空。
public void clear() // 从此队列中删除所有可用元素并将它们添加到给定集合中。此操作可能比重复轮询此队列更有效。在试图将元素添加到集合c时遇到失败抛出相关异常时可能会导致:元素不在原集合或者集合c中,或者两个集合中都没有。
public int drainTo(Collection<? super E> c) // 从该队列中最多删除给定数量的可用元素,并将它们添加到给定集合中。异常情况同上
public int drainTo(Collection<? super E> c, int maxElements) // 按适当的顺序返回此队列中元素的迭代器。元素将按从第一个(head)到最后一个(tail)的顺序返回。返回的迭代器是弱一致的。
public Iterator<E> iterator() // 返回该队列中元素的Spliterator。返回的spliterator是弱一致的。
public Spliterator<E> spliterator()
原理
成员变量
/** 队列容量,默认Integer.MAX_VALUE */
private final int capacity; /** 当前队列元素数量 */
private final AtomicInteger count = new AtomicInteger(); /**
* 链表的head
*/
transient Node<E> head; /**
* 链表的tail
*/
private transient Node<E> last; /** 由take, poll方法持有的锁 */
private final ReentrantLock takeLock = new ReentrantLock(); /** 当take的时候,如果队列为空,则等待 */
private final Condition notEmpty = takeLock.newCondition(); /** 由put, offer方法持有的锁 */
private final ReentrantLock putLock = new ReentrantLock(); /** 当put的时候,如果队列为空,则等待 */
private final Condition notFull = putLock.newCondition();
put和offer
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);
// 拿到putLock
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);
// 获取并加1;注意这里的c = 加1之前的值
c = count.getAndIncrement();
// 通知其它线程继续入队
if (c + 1 < capacity)
notFull.signal();
} finally {
putLock.unlock();
}
// 因为c的值是+1之前的,所以在此情况下:实际上队列元素数量为1,则通知等待中的take线程来拿取
if (c == 0)
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 {
// 如果队列满了,则等待指定时间;如果指定时间内有可用空间,则继续向下执行;如果超时,则返回false
while (count.get() == capacity) {
if (nanos <= 0)
return false;
nanos = notFull.awaitNanos(nanos);
}
// 入队
enqueue(new Node<E>(e));
// 先获取元素数量, 然后+1
c = count.getAndIncrement();
// 入队之后还有可用空间,则通知其它等待线程,继续入队
if (c + 1 < capacity)
notFull.signal();
} finally {
putLock.unlock();
}
if (c == 0)
signalNotEmpty();
return true;
} public boolean offer(E e) {
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;
// 注意这里不是lockInterruptibly
putLock.lock();
try {
// 如果有可用空间,则入队;否则返回false(c= -1)
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;
}
take和poll
public E take() throws InterruptedException {
E x;
int c = -1;
// 拿到计数器
final AtomicInteger count = this.count;
// 拿到takeLock
final ReentrantLock takeLock = this.takeLock;
// 优先响应中断
takeLock.lockInterruptibly();
try {
// 如果队列为空,则等待,直到有可用元素
while (count.get() == 0) {
notEmpty.await();
}
// 出队
x = dequeue();
// 获取并减1;注意这里的c = 减1之前的值
c = count.getAndDecrement();
// 通知其它等待线程,继续出队
if (c > 1)
notEmpty.signal();
} finally {
takeLock.unlock();
}
// 因为c的值是-1之前的,所以在此情况下:队列里面元素数量 = 容量大小 - 1,则通知等待中的添加线程添加
if (c == capacity)
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 {
// 如果队列为空,则等待指定时间;如果指定时间内有可用元素,则继续向下执行;如果超时,则返回null
while (count.get() == 0) {
if (nanos <= 0)
return null;
nanos = notEmpty.awaitNanos(nanos);
}
// 出队
x = dequeue();
// 先获取元素数量,然后-1
c = count.getAndDecrement();
// 通知其它等待中的take线程
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 {
// 如果有可用元素,出队;否则返回null
if (count.get() > 0) {
x = dequeue();
c = count.getAndDecrement();
if (c > 1)
notEmpty.signal();
}
} finally {
takeLock.unlock();
}
if (c == capacity)
signalNotFull();
return x;
}
peek
public E peek() {
// 队列为空,返回null
if (count.get() == 0)
return null;
final ReentrantLock takeLock = this.takeLock;
takeLock.lock();
try {
// 返回head指向的数据
Node<E> first = head.next;
if (first == null)
return null;
else
return first.item;
} finally {
takeLock.unlock();
}
}
enqueue和dequeue
// 入队操作,就是把新结点添加到队列的最后
private void enqueue(Node<E> node) {
// assert putLock.isHeldByCurrentThread();
// assert last.next == null;
// 尾指针的next指向新结点,当前尾指针指向尾指针的next;
last = last.next = node;
} // 出队操作,就是把队头移除
private E dequeue() {
// assert takeLock.isHeldByCurrentThread();
// assert head.item == null;
// 当前队头
Node<E> h = head;
// 下一任队头(head的next节点)
Node<E> first = h.next;
h.next = h; // help GC
// 任命新的队头
head = first;
// 老数据
E x = first.item;
first.item = null;
// 将老数据返回
return x;
}
通过这个出队操作,可以得知,内部的链表的head指针不存储任何数据,仅仅是标记的作用(带头结点的链表,虽然多占了一个结点的空间,但是处理逻辑变简单了)
clear
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();
}
}
优缺点
内部结构是个链表,新增和删除操作都涉及结点的创建(封装成Node)和删除(Node的回收),相比ArrayBlockingQueue是直接在数组位置进行数据的赋值与删除,开销大了一些。
相比ArrayBlockingQueue只用了一把锁,LinkedBlockingQueue使用了takeLock和putLock两把锁,分别用于阻塞队列的读写线程,也就是说,读线程和写线程可以同时运行,在多线程高并发场景,可以有更高的吞吐量。
因为队列的读写分别在头部和尾部,相互竞争的几率较小,所以用双锁可以实现更高的吞吐量;而ArrayBlockingQueue,只有一个数组,也可以用双锁,但是代码可就复杂了。
集合类源码(四)Collection之BlockingQueue(ArrayBlockingQueue, DelayQueue, LinkedBlockingQueue)的更多相关文章
- Java集合类源码解析:Vector
[学习笔记]转载 Java集合类源码解析:Vector 引言 之前的文章我们学习了一个集合类 ArrayList,今天讲它的一个兄弟 Vector.为什么说是它兄弟呢?因为从容器的构造来说,Vec ...
- J.U.C并发框架源码阅读(八)ArrayBlockingQueue
基于版本jdk1.7.0_80 java.util.concurrent.ArrayBlockingQueue 代码如下 /* * ORACLE PROPRIETARY/CONFIDENTIAL. U ...
- JDK8集合类源码解析 - HashSet
HashSet 特点:不允许放入重复元素 查看源码,发现HashSet是基于HashMap来实现的,对HashMap做了一次“封装”. private transient HashMap<E,O ...
- Java集合类源码解析:HashMap (基于JDK1.8)
目录 前言 HashMap的数据结构 深入源码 两个参数 成员变量 四个构造方法 插入数据的方法:put() 哈希函数:hash() 动态扩容:resize() 节点树化.红黑树的拆分 节点树化 红黑 ...
- JUC源码分析-集合篇(六)LinkedBlockingQueue
JUC源码分析-集合篇(六)LinkedBlockingQueue 1. 数据结构 LinkedBlockingQueue 和 ConcurrentLinkedQueue 一样都是由 head 节点和 ...
- Java集合类源码分析
常用类及源码分析 集合类 原理分析 Collection List Vector 扩充容量的方法 ensureCapacityHelper很多方法都加入了synchronized同步语句,来保 ...
- Java集合类源码解析:ArrayList
目录 前言 源码解析 基本成员变量 添加元素 查询元素 修改元素 删除元素 为什么用 "transient" 修饰数组变量 总结 前言 今天学习一个Java集合类使用最多的类 Ar ...
- Java集合类源码解析:AbstractList
今天学习Java集合类中的一个抽象类,AbstractList. 初识AbstractList AbstractList 是一个抽象类,实现了List<E>接口,是隶属于Java集合框架中 ...
- Java集合类源码解析:AbstractMap
目录 引言 源码解析 抽象函数entrySet() 两个集合视图 操作方法 两个子类 参考: 引言 今天学习一个Java集合的一个抽象类 AbstractMap ,AbstractMap 是Map接口 ...
- java集合类源码学习二
我们查看Collection接口的hierarchy时候,可以看到AbstractCollection<E>这样一个抽象类,它实现了Collection接口的部分方法,Collection ...
随机推荐
- opencv::GMM(高斯混合模型)
GMM方法概述:基于高斯混合模型期望最大化. 高斯混合模型 (GMM) 高斯分布与概率密度分布 - PDF 初始化 初始化EM模型: Ptr<EM> em_model = EM::crea ...
- 014.统一建模语言UML
1.UML 的设计目的 UML是为了简化和强化现有的大量面向对象开发方法这一目的而开发的. UML 适用于各种软件开发方法.软件生命周期的各个阶段.各种应用领域以及各种开发工具,是一种总结了以往建模技 ...
- vue路由跳转传参的两种方法
路由跳转: this.$router.push({ name: '工单列表', params: {p_camera_dev_name: 'xxx'} }); 使二级菜单呈点击状态: $('[index ...
- 5.如何基于 dubbo 进行服务治理、服务降级、失败重试以及超时重试?
作者:中华石杉 面试题 如何基于 dubbo 进行服务治理.服务降级.失败重试以及超时重试? 面试官心理分析 服务治理,这个问题如果问你,其实就是看看你有没有服务治理的思想,因为这个是做过复杂微服务的 ...
- [基础]斯坦福cs231n课程视频笔记(一) 图片分类之使用线性分类器
线性分类器的基本模型: f = Wx Loss Function and Optimization 1. LossFunction 衡量在当前的模型(参数矩阵W)的效果好坏 Multiclass SV ...
- JAVAWEB复习day01
一.基础知识 1.一个html文件开始和结束的标签<html></html> 2.html的两部分<head>设置相关信息</head>,<bod ...
- 【餐厅】 What kind of food would you like to eat tonight?
核心句型 What kind of food would you like to eat tonight? 你今晚想吃哪种菜? What would you like to eat ? 你想吃什么? ...
- 实现 Trie (前缀树)
实现一个 Trie (前缀树),包含 insert, search, 和 startsWith 这三个操作. 示例: Trie trie = new Trie(); trie.insert(" ...
- Python内容
1.Python介绍.计算机硬件.网络.变量.数据类型:列表+元组+字典+布尔值+字符串+数字+集合.格式化输出.if判断.for循环.while循环. 2.三元运算.字符编码.文件处理:r/rb(读 ...
- 使用表格 代替 txt文件(未完结)
使用xlrd读取用户数据 首先创建一个xls文件 xlrd模块的简单使用 添加处理execl的接口: 使用excel生成测试报表