DelayQueue阅读笔记
一、简介
元素最大容量没有限制,内部采用PriorityQueue优先队列存储元素,添加和获取元素都进行了ReentrantLock加锁,同时集合中没有元素时进行等待采用的Condition条件锁。所有操作都加入了ReentrantLock锁
二、继承关系图
三、存储结构
PriorityQueue优先队列
四、源码分析
属性
/** 用于控制并发的锁 */
private final transient ReentrantLock lock = new ReentrantLock();
/** 存储元素集合:优先级队列 */
private final PriorityQueue<E> q = new PriorityQueue<E>();
/** 用于标记当前是否有线程在排队(仅用于取元素时) */
private Thread leader = null;
/** 可用条件锁:当集合为null就awrit,当添加元素则signal 。用于表示现在是否有可取的元素*/
private final Condition available = lock.newCondition();
构造
/** 默认无参构造 */
public DelayQueue() {}
/** 构造构造,可设定初始化的加入的元素集合,不可为null */
public DelayQueue(Collection<? extends E> c) {
// 调用的是父类 AbstractQueue 的addAll方法
this.addAll(c);
}
/** AbstractQueue.addAll(Collection<? extends E> c) */
public boolean addAll(Collection<? extends E> c) {
if (c == null)
throw new NullPointerException();
if (c == this)
throw new IllegalArgumentException();
boolean modified = false;//默认添加失败
// 循环取出集合元素加入到当前集合
for (E e : c)
if (add(e))
// 如果添加成功则修改添加成功
modified = true;
return modified;
}
主要方法
入队操作
- add、put、offer延时 都是直接调用的offer,没做任何变化
public boolean add(E e) {
return offer(e);
}
public boolean offer(E e) {
final ReentrantLock lock = this.lock;
// 加锁
lock.lock();
try {
// 调用优先队列PriorityQueued.offer方法进行元素元素添加
q.offer(e);
if (q.peek() == e) {
// 判断添加的元素是否是优先头节点
// 如果是就直接设置leader为null,且调用available条件锁唤醒wait中的等待线程
leader = null;
available.signal();
}
// 返回添加成功
return true;
} finally {
// 释放锁
lock.unlock();
}
}
public void put(E e) {
offer(e);
}
public boolean offer(E e, long timeout, TimeUnit unit) {
return offer(e);
}
出队操作
- remove() :删除元素,并且返回被删除的元素,如果为null或过期抛出异常
- poll():删除元素,并且返回被删除的元素,如果为null或过期返回null,
- task():
- 判断堆顶元素是否为null、为null就直接阻塞队列,
- 判断堆顶元素是否到期,到期了直接调用PriorityQueue.poll进行弹出元素,成功返回旧值,
- poll(long timeout, TimeUnit unit):直接获取元素,如果在指定时间没有获取元素,直接返回null
/** 父类:AbstractQueue.remove方法 */
public E remove() {
// 调用自身的polll()函数
E x = poll();
if (x != null)
// 如果元素不为null 返回被删除的元素
return x;
else
// 元素为null,抛出NoSuchElementException异常
throw new NoSuchElementException();
}
/** 取出队列元素,并返回,如果不存在返回null */
public E poll() {
final ReentrantLock lock = this.lock;
// 加锁
lock.lock();
try {
// 获取优先队列第一个元素
E first = q.peek();
if (first == null || first.getDelay(NANOSECONDS) > 0)
// 如果第一个元素为null
// 或者第一个元素到期了
// 返回null
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)
// 如果堆顶元素为null,说明队列中没有元素,则加入到条件锁的等待线程队列
available.await();
else {
// 堆顶元素的剩余延迟,延迟只有低于0及以下才可以获取元素
long delay = first.getDelay(NANOSECONDS);
// 如果小于0 说明已到期,直接调用poll()弹出栈顶元素
if (delay <= 0)
return q.poll();
// 程序走到这里,说明第一个元素已经超时,
// 所以直接设置为null,便于gc,因为有可能其他线程弹出了元素
first = null; // don't retain ref while waiting
if (leader != null)
// 如果leader线程不是null,
// 说明有其他线程在等待,则直接线程等待
available.await();
else {
// 如果leader线程为null,
// 把当前线程设置为leader线程
Thread thisThread = Thread.currentThread();
leader = thisThread;
try {
// 等待delay时候后自动醒来
// 醒来后把leader置为nulll并重新加入循环判断顿对元素是否到期
// 这里既使醒过来后也不一定能获取到元素
// 因为有可能其他线程先一步获取了并弹出了堆顶元素
// 条件锁的唤醒分成两部,先从condition的队列中出队
// 再入队到AQS的队列中,当其他线程抵用LockSupport.unpark(t)的时候
available.awaitNanos(delay);
} finally {
if (leader == thisThread)
// 如果当前线程还leader,就把lead设置我null,
// 因为相等说明我已经取过元素了,可以退出了
// 让其他线程有机会获取元素
leader = null;
}
}
}
}
} finally {
/// 成功出队后,如果leader为null且堆顶还有元素且堆顶元素,就唤醒下一个等待的线程
if (leader == null && q.peek() != null)
// 只是把等待的线程放在AQS的队列李雷,并不是真正的唤醒
available.signal();
// 释放锁
lock.unlock();
}
}
/** 取元素,如果没有元素就等待timout时间,然后再重新获取,如果还是timeout时间内还没有获取就返回null */
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) {
// 如果元素为null进入
if (nanos <= 0)
// 等待时间已过,直接返回null
return null;
else
// 直接等待纳秒时间,然后重新循环
nanos = available.awaitNanos(nanos);
} else {
// 如果不为null,就获取元素的剩余延迟,也就是延迟时间没过,不能获取元素
long delay = first.getDelay(NANOSECONDS);
if (delay <= 0)
// 延迟时间已过去。直接取出元素
return q.poll();
if (nanos <= 0)
// 等待时间已过直接返回null
return null;
first = null; // don't retain ref while waiting 帮忙gc
if (nanos < delay || leader != null)
// 如果等待时间小于延迟时间,那么就直接加入条件等待队列等待,
// 如果leader线程有等待,那么就直接加入条件等待队列等待,
// 因为还有其他线程需要获取元素嘛
nanos = available.awaitNanos(nanos);
else {
// 进入这里说明leader == null,直接把当前线程赋值给thisThread
Thread thisThread = Thread.currentThread();
// 直接把当前线程赋值给leader,相当于直接占用等待时间,反正是null嘛
leader = thisThread;
try {
// 等待指定延迟时间(进入这里说明超时时间大于等于延迟时间)
// 等待后的剩余时间,
long timeLeft = available.awaitNanos(delay);
// 重新计算nanos时间
nanos -= delay - timeLeft;
} finally {
if (leader == thisThread)
// 如果他们一样,就释放释放leader,
// 然后重新循环获取元素
leader = null;
}
}
}
}
} finally {
if (leader == null && q.peek() != null)
// 元素获取成功后,也可能获取的是null,超时没有获取
// 如果没有线程在等待,且优先队列不等于null
// 那么就唤醒处于available.await等待的线程来获取元素
available.signal();
lock.unlock();
}
}
获取队列顶元素
public E peek() {
final ReentrantLock lock = this.lock;
// 加锁
lock.lock();
try {
return q.peek();
} finally {
// 释放锁
lock.unlock();
}
}
删除元素
public boolean remove(Object o) {
final ReentrantLock lock = this.lock;
// 加锁
lock.lock();
try {
return q.remove(o);
} finally {
// 释放锁
lock.unlock();
}
}
补充
使用demo
public class DelayTest {
public static void main(String[] args) {
DelayQueue<Message> queue = new DelayQueue<>();
long now = System.currentTimeMillis();
new Thread(() -> {
while (true) {
try {
// 1000.2000.5000。7000.8000
System.out.println(queue.take().deadline - now);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
queue.add(new Message(now + 5000));
queue.add(new Message(now + 8000));
queue.add(new Message(now + 2000));
queue.add(new Message(now + 1000));
queue.add(new Message(now + 7000));
}
}
class Message implements Delayed {
long deadline;
public Message(long deadline) {
this.deadline = deadline;
}
@Override
public long getDelay(TimeUnit unit) {
return deadline - System.currentTimeMillis();
}
@Override
public int compareTo(Delayed o) {
return (int) (getDelay(TimeUnit.MILLISECONDS) - o.getDelay(TimeUnit.MILLISECONDS));
}
@Override
public String toString() {
return String.valueOf(deadline);
}
}
五、总结
- 阻塞对垒
- 内部存储结构使用优先级队列
- 使用个重入锁和条件锁来控制并发安全
- 常用语定时任务