BlockingQueue为阻塞队列的顶级接口,下边有几种阻塞队列:
ArrayBlockingQueue:由数组结构组成的有界阻塞队列。
LinkedBlockingQueue:由链表结构组成的有界(但大小默认值为Integer.MAX_VALUE)阻塞队列。
PriorityBlockingQueue:支持优先级排序的无界阻塞队列。
DelayQueue:使用优先级队列实现的延迟无界阻塞队列。
SynchronousQueue:不存储元素的阻塞队列,也即单个元素的队列。
LinkedTransferQueue:由链表结构组成的无界阻塞队列。
LinkedBlockingDeque:由链表组成的双向阻塞队列。
操作 | 抛出异常 | 返回特殊值 |
---|---|---|
Insert | add(e) | offer(e) |
Remove | remove() | poll() |
Examine | element() | peek() |
操作 | 抛出异常 | 返回特殊值 | 阻塞 | 超时 |
---|---|---|---|---|
Insert | add(e) | offer(e) | put(e) | offer(e, time, unit) |
Remove | remove() | poll() | take() | poll(time, unit) |
Examine | element() | peek() | 无 | 无 |
BlockingQueue虽然比起Queue在操作上提供了更多的支持,但是它在使用的使用也应该如下的几点:
BlockingQueue中是不允许添加null的,该接受在声明的时候就要求所有的实现类在接收到一个null的时候,都应该抛出NullPointerException。
BlockingQueue是线程安全的,因此它的所有和队列相关的方法都具有原子性。但是对于那么从Collection接口中继承而来的批量操作方法,比如addAll(Collection e)等方法,BlockingQueue的实现通常没有保证其具有原子性,因此我们在使用的BlockingQueue,应该尽可能地不去使用这些方法。
BlockingQueue主要应用于生产者与消费者的模型中,其元素的添加和获取都是极具规律性的。但是对于remove(Object o)这样的方法,虽然BlockingQueue可以保证元素正确的删除,但是这样的操作会非常响应性能,因此我们在没有特殊的情况下,也应该避免使用这类方法。
ArrayBlockingQueue
是一个线程安全的、基于数组、有界的、阻塞的、FIFO 队列。试图向已满队列中放入元素会导致操作受阻塞;试图从空队列中提取元素将导致类似阻塞。ArrayBlockingQueue读写共享一把锁
/** 数据存放在Object数组中 **/
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;
/** 可重入锁:Main lock guarding all access */
final ReentrantLock lock;
/** take的队列:Condition for waiting takes */
private final Condition notEmpty;
/** put的队列:Condition for waiting puts */
private final Condition notFull;
public ArrayBlockingQueue(int capacity, boolean fair) {
if (capacity <= 0)
throw new IllegalArgumentException();
this.items = new Object[capacity];
lock = new ReentrantLock(fair);
notEmpty = lock.newCondition();
notFull = lock.newCondition();
}
ArrayBlockingQueue在实例化的时候创建了两个条件队列notEmpty和notFull两个条件队列,这两个队列公用一把ReentrantLock锁
两个队列均为单向队列,由firstNode和lastNode指针指向首尾,节点为node节点(AQS ReentrantLock知识此处不做赘述)
public void put(E e) throws InterruptedException {
checkNotNull(e);
//先获取锁
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
//判断数组中数量是否达到阻塞队列的最大长度,达到则阻塞
while (count == items.length)
notFull.await();
enqueue(e);
} finally {
lock.unlock();
}
}
private void enqueue(E x) {
// assert lock.getHoldCount() == 1;
// assert items[putIndex] == null;
final Object[] items = this.items;
//直接给数组赋值,putIndex为当前值的数组索引
items[putIndex] = x;
//如果数组已经满了
if (++putIndex == items.length)
putIndex = 0;
count++;
//条件唤醒等待的take线程
notEmpty.signal();
}
public E take() throws InterruptedException {
//先获取锁
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
//如果数组中的数量为0,则阻塞等待
while (count == 0)
notEmpty.await();
return 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;
//如果下次拿数据的索引位置等于数组的长度,则设置为0即表示数组中数据已经拿完
if (++takeIndex == items.length)
takeIndex = 0;
//数量减一
count--;
if (itrs != null)
itrs.elementDequeued();
//条件唤醒等待的put线程
notFull.signal();
return x;
}
void elementDequeued() {
// assert lock.getHoldCount() == 1;
//如果队列为空
if (count == 0)
queueIsEmpty();
else if (takeIndex == 0)
takeIndexWrapped();
}
void queueIsEmpty() {
// assert lock.getHoldCount() == 1;
//遍历链表
for (Node p = head; p != null; p = p.next) {
Itr it = p.get();
if (it != null) {
//将引用清除
p.clear();
//通知Itr队列空了,将各参数置为null或者特殊index值
it.shutdown();
}
}
head = null;
itrs = null;
}
void takeIndexWrapped() {
// assert lock.getHoldCount() == 1;
cycles++;
//遍历链表,o表示上一个有效节点,p表示当前遍历的节点
for (Node o = null, p = head; p != null;) {
final Itr it = p.get();
final Node next = p.next;
if (it == null || it.takeIndexWrapped()) {
// unlink p
// assert it == null || it.isDetached();
p.clear();
p.next = null;
//之前的节点是无效节点,重置head,把next之前的节点都移除了
if (o == null)
head = next;
else
//移除p这一个节点
o.next = next;
} else {
//保存上一个有效节点
o = p;
}
//处理下一个节点
p = next;
}
//没有有效节点,itrs置为null
if (head == null) // no more iterators to track
itrs = null;
}
boolean takeIndexWrapped() {
// assert lock.getHoldCount() == 1;
if (isDetached())
return true;
if (itrs.cycles - prevCycles > 1) {
// All the elements that existed at the time of the last
// operation are gone, so abandon further iteration.
//上一次操作时存在的所有元素都消失了,因此放弃进一步的迭代
shutdown();
return true;
}
return false;
}
// 容量,指定容量就是有界队列
private final int capacity;
// 元素数量,用原子操作类的原因在于有两个线程都会操作需要保证可见性
private final AtomicInteger count = new AtomicInteger();
// 链表头 本身是不存储任何元素的,初始化时item指向null
transient Node<E> head;
// 链表尾
private transient Node<E> last;
// take锁 锁分离,提高效率
private final ReentrantLock takeLock = new ReentrantLock();
// notEmpty条件
// 当队列无元素时,take锁会阻塞在notEmpty条件上,等待其它线程唤醒
private final Condition notEmpty = takeLock.newCondition();
// put锁
private final ReentrantLock putLock = new ReentrantLock();
// notFull条件
// 当队列满了时,put锁会会阻塞在notFull上,等待其它线程唤醒
private final Condition notFull = putLock.newCondition();
//典型的单链表结构
static class Node<E> {
E item; //存储元素
Node<E> next; //后继节点 单链表结构
Node(E x) { item = x; }
}
//初始化LinkedBlockingQueue,设置默认的容量
public LinkedBlockingQueue() {
this(Integer.MAX_VALUE);
}
//初始化LinkedBlockingQueue,设置指定的容量
public LinkedBlockingQueue(int capacity) {
if (capacity <= 0) throw new IllegalArgumentException();
this.capacity = capacity;
last = head = new Node<E>(null);
}
// 初始化LinkedBlockingQueue,将传入的集合元素加入到队列中
public LinkedBlockingQueue(Collection<? extends E> c) {
// 初始化,设置默认容量
this(Integer.MAX_VALUE);
final ReentrantLock putLock = this.putLock;
putLock.lock(); // Never contended, but necessary for visibility
try {
int n = 0;
for (E e : c) {
// 如果元素为null,则抛出NPE
if (e == null)
throw new NullPointerException();
// 如果元素数量已经达到设置的容量,则抛出队列已满的异常
if (n == capacity)
throw new IllegalStateException("Queue full");
// 创建节点,并入队
enqueue(new Node<E>(e));
// 元素数量加1
++n;
}
// 设置元素数量
count.set(n);
} finally {
putLock.unlock();
}
}
public void put(E e) throws InterruptedException {
// 不允许null元素
if (e == null) throw new NullPointerException()
int c = -1;
// 新建一个节点
Node<E> node = new Node<E>(e);
final ReentrantLock putLock = this.putLock;
final AtomicInteger count = this.count;
// 使用put锁加锁
putLock.lockInterruptibly();
try {
// 如果队列满了,就阻塞在notFull上等待被其它线程唤醒(阻塞生产者线程)
while (count.get() == capacity) {
notFull.await();
}
// 队列不满,就入队
enqueue(node);
// 队列长度加1,返回原值
c = count.getAndIncrement();
// 如果现队列长度小于容量,notFull条件队列转同步队列,准备唤醒一个阻塞在notFull条件上的线程(可以继续入队)
// 这里为啥要唤醒一下呢?因为存在情况是,没人获取时,队列满了而且还不断有人塞数据,此时会一大批线程被阻塞,现在有空余位置了,应该被唤醒
if (c + 1 < capacity)
notFull.signal();
} finally {
//解锁,真正唤醒生产者线程
putLock.unlock();
}
// 如果原队列长度为0,现在加了一个元素后立即唤醒阻塞在notEmpty上的线程
if (c == 0)
signalNotEmpty();
}
private void enqueue(Node<E> node) {
// assert putLock.isHeldByCurrentThread();
// assert last.next == null;
// 直接加到last后面,last指向入队元素
last = last.next = node;
}
private void signalNotEmpty() {
final ReentrantLock takeLock = this.takeLock;
// 加take锁
takeLock.lock();
try {
// notEmpty条件队列转同步队列,准备唤醒阻塞在notEmpty上的线程
notEmpty.signal();
} finally {
// 解锁,真正唤醒消费者线程
takeLock.unlock();
}
}
public E take() throws InterruptedException {
E x;
int c = -1;
final AtomicInteger count = this.count;
final ReentrantLock takeLock = this.takeLock;
//加锁
takeLock.lockInterruptibly();
try {
// 如果队列无元素,则阻塞在notEmpty条件上(消费者线程阻塞)
while (count.get() == 0) {
notEmpty.await();
}
// 否则,出队
x = dequeue();
c = count.getAndDecrement();
// 如果取之前队列长度大于1,notEmpty条件队列转同步队列,准备唤醒阻塞在notEmpty上的线程,原因与入队同理
if (c > 1)
notEmpty.signal();
} finally {
//解锁,真正唤醒消费者线程
takeLock.unlock();
}
// 为什么队列是满的才唤醒阻塞在notFull上的线程呢?
// 因为唤醒是需要加putLock的,这是为了减少锁的次数,所以,这里索性在放完元素就检测一下,未满就唤醒其它notFull上的线程,
// 这也是锁分离带来的代价
// 如果取之前队列长度等于容量(已满),则唤醒阻塞在notFull的线程
if (c == capacity)
signalNotFull();
return x;
}
private E dequeue() {
// assert takeLock.isHeldByCurrentThread();
// assert head.item == null;
// head节点本身是不存储任何元素的
// 这里把head删除,并把head下一个节点作为新的值
// 并把其值置空,返回原来的值
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;
}
private void signalNotFull() {
final ReentrantLock putLock = this.putLock;
//加锁
putLock.lock();
try {
//notFull条件队列转同步队列,准备唤醒阻塞在notFull上的线程
notFull.signal();
} finally {
//解锁,这才会真正的唤醒生产者线程
putLock.unlock();
}
}
【1】无界阻塞队列,可以指定容量,默认为 Integer.MAX_VALUE,先进先出,存取互不干扰
【2】数据结构:链表(可以指定容量,默认为 Integer.MAX_VALUE,内部类Node存储元素)
【3】锁分离:存取互不干扰,存取操作的是不同的Node对象(takeLock【取Node节点保证前驱后继不乱】,putLock【存Node节点保证前驱后继不乱】,删除时则两个锁一起加)【这是最大的亮点】
【4】阻塞对象(notEmpty【出队:队列count=0,无元素可取时,阻塞在该对象上】,notFull【入队:队列count=capacity,放不进元素时,阻塞在该对象上】)
【5】入队,从队尾入队,由last指针记录。
【6】出队,从队首出队,由head指针记录。
【7】线程池中采用LinkedBlockingQueue而不采用ArrayBlockingQueue的原因便是因为锁分离带来了性能的提升,大大提高队列的吞吐量。
单生产者,单消费者 用 LinkedBlockingqueue
多生产者,单消费者 用 LinkedBlockingqueue
单生产者 ,多消费者 用 ConcurrentLinkedQueue
多生产者 ,多消费者 用 ConcurrentLinkedQueue
ConcurrentLinkedQueue是一个无锁化、非阻塞、线程安全的单向队列,JDK1.5提供。ConcurrentLinkedQueue实现的依赖cas操作和java的valotile语义。
public class ConcurrentLinkedQueue<E> extends AbstractQueue<E>
implements Queue<E>, java.io.Serializable {
private transient volatile Node<E> head;
private transient volatile Node<E> tail;
//无参构造器
public ConcurrentLinkedQueue() {
head = tail = new Node<E>(null);
}
//有参构造器,初始化传入集合
public ConcurrentLinkedQueue(Collection<? extends E> c) {
Node<E> h = null, t = null;
for (E e : c) {
checkNotNull(e);
Node<E> newNode = new Node<E>(e);
if (h == null)
h = t = newNode;
else {
t.lazySetNext(newNode);
t = newNode;
}
}
if (h == null)
h = t = new Node<E>(null);
head = h;
tail = t;
}
}
public boolean offer(E e) {
//判断是否为空
checkNotNull(e);
//给当前值新建一个节点
final Node<E> newNode = new Node<E>(e);
// 2.开始循环,获取尾结点
for (Node<E> t = tail, p = t;;) {
// t和p默认是是尾结点
// q是p的下一个结点
Node<E> q = p.next;
//如果next为空,说明p是尾结点
if (q == null) {
// 通过cas的方式放入新结点,未成功则继续循环放入
if (p.casNext(null, newNode)) {
// 当前p如果不等于t 则更新尾结点, p!=t是因为当前tail不是尾结点,P重新赋值了头节点导致的P和T不同
if (p != t) // hop two nodes at a time
casTail(t, newNode); // Failure is OK.
return true;
}
}
// tail未指向尾结点,并且p==q 说明当前tail为闭环,可能当前节点已经舍弃
else if (p == q)
// 如果p的next等于p,说明p已经被删除了(已经出队了)
// 重新设置p的值
p = (t != (t = tail)) ? t : head;
else
// t后面还有值,重新设置p的值
p = (p != t && t != (t = tail)) ? t : q;
}
}
快照图
public E poll() {
restartFromHead:
for (;;) {
// p节点表示首节点,即需要出队的节点
for (Node<E> h = head, p = h, q;;) {
E item = p.item;
// 如果p节点的元素不为null,则通过CAS来设置p节点引用的元素为null,如果成功则返回p节点的元素
if (item != null && p.casItem(item, null)) {
// 如果p != h,则更新head
if (p != h) // hop two nodes at a time
updateHead(h, ((q = p.next) != null) ? q : p);
return item;
}
// 如果头节点的元素为空或头节点发生了变化,这说明头节点已经被另外一个线程修改了。
// 那么获取p节点的下一个节点,如果p节点的下一节点为null,则表明队列已经空了
else if ((q = p.next) == null) {
updateHead(h, p);
return null;
}
// p == q,则使用新的head重新开始
else if (p == q)
continue restartFromHead;
// 如果下一个元素不为空,则将头节点的下一个节点设置成头节点
else
p = q;
}
}
}
final void updateHead(Node<E> h, Node<E> p) {
if (h != p && casHead(h, p))
// 将旧的头结点h的next域指向为h
h.lazySetNext(h);
}
PriorityBlockingQueue是一个无界的基于数组的优先级阻塞队列,数组的默认长度是11,也可以指定数组的长度,且可以无限的扩充,直到资源消耗尽为止,每次出队都返回优先级别最高的或者最低的元素。默认情况下元素采用自然顺序升序排序,当然我们也可以通过构造函数来指定Comparator来对元素进行排序。需要注意的是PriorityBlockingQueue不能保证同优先级元素的顺序。
//默认容量
private static final int DEFAULT_INITIAL_CAPACITY = 11;
//最大容量设定
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
//存放数据的数组
private transient Object[] queue;
//元素个数
private transient int size;
//排序规则(比较器)
private transient Comparator<? super E> comparator;
//独占锁
private final ReentrantLock lock;
//队列为空的时候的阻塞队列
private final Condition notEmpty;
//用于分配的CAS自旋锁
private transient volatile int allocationSpinLock;
//只用于序列化的普通优先队列
private PriorityQueue<E> q;
//无参构造器
public PriorityBlockingQueue() {
this(DEFAULT_INITIAL_CAPACITY, null);
}
//初始化容量
public PriorityBlockingQueue(int initialCapacity) {
this(initialCapacity, null);
}
//初始化容量并指定自定义的比较器
public PriorityBlockingQueue(int initialCapacity,
Comparator<? super E> comparator) {
if (initialCapacity < 1)
throw new IllegalArgumentException();
this.lock = new ReentrantLock();
this.notEmpty = lock.newCondition();
this.comparator = comparator;
this.queue = new Object[initialCapacity];
}
//初始化并添加集合
public PriorityBlockingQueue(Collection<? extends E> c) {
this.lock = new ReentrantLock();
this.notEmpty = lock.newCondition();
boolean heapify = true; // true if not known to be in heap order
boolean screen = true; // true if must screen for nulls
if (c instanceof SortedSet<?>) {
SortedSet<? extends E> ss = (SortedSet<? extends E>) c;
this.comparator = (Comparator<? super E>) ss.comparator();
heapify = false;
}
else if (c instanceof PriorityBlockingQueue<?>) {
PriorityBlockingQueue<? extends E> pq =
(PriorityBlockingQueue<? extends E>) c;
this.comparator = (Comparator<? super E>) pq.comparator();
screen = false;
if (pq.getClass() == PriorityBlockingQueue.class) // exact match
heapify = false;
}
Object[] a = c.toArray();
int n = a.length;
// If c.toArray incorrectly doesn't return Object[], copy it.
if (a.getClass() != Object[].class)
a = Arrays.copyOf(a, n, Object[].class);
if (screen && (n == 1 || this.comparator != null)) {
for (int i = 0; i < n; ++i)
if (a[i] == null)
throw new NullPointerException();
}
this.queue = a;
this.size = n;
if (heapify)
heapify();
}
public void put(E e) {
//put和offer一样,不需要阻塞
offer(e); // never need to block
}
public boolean offer(E e) {
if (e == null)
throw new NullPointerException();
final ReentrantLock lock = this.lock;
lock.lock();
int n, cap;
Object[] array;
//当元素个数大于队列长度则先进行扩容
while ((n = size) >= (cap = (array = queue).length))
//执行扩容操作
tryGrow(array, cap);
try {
Comparator<? super E> cmp = comparator;
if (cmp == null)
//使用默认的比较器插入数据
siftUpComparable(n, e, array);
else
//使用自定义的比较器插入数据
siftUpUsingComparator(n, e, array, cmp);
//大小+1
size = n + 1;
// 当我们入队成功之后,说明队列中已经有元素了,则唤醒被notEmpty阻塞的线程,也就是可以继续执行出队操作了
notEmpty.signal();
} finally {
lock.unlock();
}
return true;
}
// k为队列中元素的数量,x为要入队的元素,array就是我们的队列
private static <T> void siftUpComparable(int k, T x, Object[] array) {
Comparable<? super T> key = (Comparable<? super T>) x;
while (k > 0) {
// 这里拓展一下位运算,<<表示左移移,不分正负数,低位补0
// >>表示右移,如果该数为正,则高位补0,若为负数,则高位补1
// >>>表示无符号右移,也叫逻辑右移,即若该数为正,则高位补0,而若该数为负数,则右移后高位同样补0
// 这里的意思等价于parent = (k - 1) / 2,通过上面的二叉堆特性,我们知道了这个parent其实就是k的父节点
int parent = (k - 1) >>> 1;
// 取出队列中间的元素,设置为e
Object e = array[parent];
// 如果要入队的元素大于等于e的话,就直接break掉,最后将这个元素加在数组最后即可
if (key.compareTo((T) e) >= 0)
break;
// 如果上面的if条件不满足,则将e与k的位置交换,然后再跟父节点比大小
array[k] = e;
k = parent;
}
array[k] = key;
}
这段代码采用了二叉堆向上调整算法
**二叉堆的概念:**二叉堆是一种特殊的堆,二叉堆是完全二元树(二叉树)或者是近似完全二元树(二叉树)。二叉堆有两种:最大堆和最小堆。最大堆:父结点的键值总是大于或等于任何一个子节点的键值;最小堆:父结点的键值总是小于或等于任何一个子节点的键值。
举例1:数组[1,2,3,4,5,6]中添加7
通过(n - 1) / 2找到父节点的值是3,发现 7 > 3,那么直接将7加在数组尾部即可
举例2:数组[1,2,4,5,6,7]中添加3
通过(n - 1) / 2找到父节点的值是3,发现 3 > 4,此时就将3和4的位置进行调换
//与PriorityBlockingQueue.siftUpComparable几乎一模一样,仅仅是比较器不一样
private static <T> void siftUpUsingComparator(
int k, T x, Object[] es, Comparator<? super T> cmp) {
while (k > 0) {
int parent = (k - 1) >>> 1;
Object e = es[parent];
if (cmp.compare(x, (T) e) >= 0)
break;
es[k] = e;
k = parent;
}
es[k] = x;
}
private void tryGrow(Object[] array, int oldCap) {
//必须释放然后重新获得主锁,这一步的意义在于所有操作共享一把锁,在进行扩容时(因为写已满),释放锁(不能写,要等待扩容完才能写),提供给读操作
lock.unlock(); // must release and then re-acquire main lock
Object[] newArray = null;
// CAS 轻量级锁加锁,避免并发扩容;加锁操作就是CAS将0变为1
if (allocationSpinLock == 0 &&
ALLOCATIONSPINLOCK.compareAndSet(this, 0, 1)) {
try {
// 扩容步长,旧值小于64时,变为两倍+2。大于64时,变为1.5倍。
int newCap = oldCap + ((oldCap < 64) ?
(oldCap + 2) : // grow faster if small
(oldCap >> 1));
// 超过最大容量,可能内存溢出
if (newCap - MAX_ARRAY_SIZE > 0) { // possible overflow
//+1后判断是否已经内存溢出
int minCap = oldCap + 1;
if (minCap < 0 || minCap > MAX_ARRAY_SIZE)
throw new OutOfMemoryError();
//如果+1没有内存溢出,直接设置为最大值,最大值肯定小于1.5xoldCap
newCap = MAX_ARRAY_SIZE;
}
// 创建新数组
if (newCap > oldCap && queue == array)
newArray = new Object[newCap];
} finally {
//释放锁,1修改为0
allocationSpinLock = 0;
}
}
// 并发扩容时,线程让出 cpu 执行时间,给其他线程执行,自己稍后执行,原因:加锁不成功必然有其他线程也在扩容,在等待过程中让出资源给其他线程利用
if (newArray == null) // back off if another thread is allocating
Thread.yield();
//重新加锁
lock.lock();
//变更内存指向,利用内存拷贝复制旧数据
if (newArray != null && queue == array) {
queue = newArray;
System.arraycopy(array, 0, newArray, 0, oldCap);
}
}
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
//获取锁,考虑中断
lock.lockInterruptibly();
E result;
try {
//如果队列为空,等待;队列不为空,执行出队操作
while ( (result = dequeue()) == null)
notEmpty.await();
} finally {
//释放锁
lock.unlock();
}
return result;
}
private E dequeue() {
int n = size - 1;
//n<0说明队列为空
if (n < 0)
return null;
else {
Object[] array = queue;
//第一个始终是最大或者最小的对象
E result = (E) array[0];
E x = (E) array[n];
//将数据最后一个数据设为空
array[n] = null;
Comparator<? super E> cmp = comparator;
if (cmp == null)
//使用默认比较器进行数据调整
siftDownComparable(0, x, array, n);
else
//使用自定义的比较器进行数据调整
siftDownUsingComparator(0, x, array, n, cmp);
size = n;
return result;
}
}
//在dequeue方法中,k是要出队的数据索引0(即出队的元素),x是数组最后一个数据,n是数组最后一个数据的索引
private static <T> void siftDownComparable(int k, T x, Object[] array,
int n) {
if (n > 0) {
Comparable<? super T> key = (Comparable<? super T>)x;
//n/2
int half = n >>> 1; // loop while a non-leaf
//在数组中,位置K的节点的子节点的下标肯定小于half
while (k < half) {
//k<<1即 kx2 获取k节点的左子节点
int child = (k << 1) + 1; // assume left child is least
//获取左子节点对应的值
Object c = array[child];
//获取k节点右子节点的索引
int right = child + 1;
//比较右节点的索引是否小于队列长度和左右子节点的值进行比较
if (right < n &&
((Comparable<? super T>) c).compareTo((T) array[right]) > 0)
//如果左子节点大于右子节点,c取较小的子节点
c = array[child = right];
//key小于等于子节点
if (key.compareTo((T) c) <= 0)
//结束
break;
//如果key大于子节点,交换位置,将较小的子节点的值放到父结点K的位置
array[k] = c;
//此时需要比较和交换的目标位置变成了child,在while循环中继续向下比较
k = child;
}
//将最后一个元素的值放在K这个位置
array[k] = key;
}
}
//与PriorityBlockingQueue.siftDownComparable几乎一模一样,仅仅是比较器不一样
private static <T> void siftDownUsingComparator(int k, T x, Object[] array,
int n,
Comparator<? super T> cmp) {
if (n > 0) {
int half = n >>> 1;
while (k < half) {
int child = (k << 1) + 1;
Object c = array[child];
int right = child + 1;
if (right < n && cmp.compare((T) c, (T) array[right]) > 0)
c = array[child = right];
if (cmp.compare(x, (T) c) <= 0)
break;
array[k] = c;
k = child;
}
array[k] = x;
}
}
DelayQueue 是一个通过PriorityBlockingQueue实现延迟获取元素的无界阻塞队列,其中添加进该队列的元素必须实现Delayed接口(指定延迟时间),而且只有在延迟期满后才能从中提取元素。注意:不能将null元素放置到这种队列中
//用于控制并发的锁
private final transient ReentrantLock lock = new ReentrantLock();
//优先级队列
private final PriorityQueue<E> q = new PriorityQueue<E>();
//用于标记当前是否有线程在排队(仅用于取元素时)
private Thread leader = null;
//条件,用于表示现在是否有可取的元素
private final Condition available = lock.newCondition();
public DelayQueue() {}
public DelayQueue(Collection<? extends E> c) {
this.addAll(c);
}
public void put(E e) {
offer(e);
}
public boolean offer(E e) {
final ReentrantLock lock = this.lock;
//加锁
lock.lock();
try {
//调用PriorityQueue的offer方法,源码参考PriorityBlockingQueue,类似
q.offer(e);
// 如果队列的第一个元素是插入的元素(插入成功),则设置 leader=null,并且通知等待锁的线程。
if (q.peek() == e) {
leader = null;
available.signal();
}
return true;
} 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);
// 如果小于0说明已到期,直接调用poll()方法弹出堆顶元素
if (delay <= 0)
return q.poll();
//如果delay大于0 下面要开始阻塞了
first = null; // don't retain ref while waiting
// 如果前面有其它线程在等待,直接进入等待
if (leader != null)
available.await();
else {
// 如果leader为null,把当前线程赋值给它
Thread thisThread = Thread.currentThread();
leader = thisThread;
try {
// 等待delay时间后自动醒过来
// 醒过来后把leader置空并重新进入循环判断堆顶元素是否到期
// 这里即使醒过来后也不一定能获取到元素
// 因为有可能其它线程先一步获取了锁并弹出了堆顶元素
// 条件锁的唤醒分成两步,先从Condition的队列里出队
// 再入队到AQS的队列中,当其它线程调用LockSupport.unpark(t)的时候才会真正唤醒
available.awaitNanos(delay);
} finally {
// 如果leader还是当前线程就把它置为空,让其它线程有机会获取元素
if (leader == thisThread)
leader = null;
}
}
}
}
} finally {
// 成功出队后,如果leader为空且堆顶还有元素,就唤醒下一个等待的线程
if (leader == null && q.peek() != null)
//signal()只是把等待的线程放到AQS的队列里面,并不是真正的唤醒
available.signal();
//解锁,这才是真正的唤醒
lock.unlock();
}
}