DelayQueue原理探究
DelayQueue是一个无界阻塞延迟队列,队列中的每个元素都有过期时间,只有过期元素才会出列,队列头元素是最快要过期的元素.
(1). 结构
内部使用PriorityQueue(二叉堆实现的队列)存放数据,使用ReentrantLock实现线程同步.要知道每个元素的过期时间,所有入队的元素要实现Delayed接口.内部使用优先级队列,所以还要实现元素之间相互比较的元素.
条件变量available与lock锁是对应的,目的是实现线程间同步.
正在操作堆顶元素的take()方法线程会被标记为leader.
(2). 主要函数原理讲解
1). offer操作
public boolean offer(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
// 直接使用了内部的PriorityQueue的方法,过程同上一节
q.offer(e);
// 如果当前元素为堆顶节点,则说明没有出队线程在操作现在的堆顶元素了,将leader置为null
// 并且唤醒其他所有的条件阻塞(堆顶元素是否过期重新判断)
if (q.peek() == e) {
leader = null;
available.signal();
}
return true;
} finally {
lock.unlock();
}
}
2). take操作
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;
// 有其他take方法线程在使用堆顶元素,那么挂起
if (leader != null)
available.await();
else {
// 这里说明对于堆顶元素,没有多个线程在竞争
// 这样的情况出现在其他线程处于短时间挂起等待堆顶元素过期的时候
// 这个线程也进行短时间挂起
// 至于下一次唤醒自旋时那个线程能成功进行出队操作,就很随机了
// 当前线程获取leader标记,虽然没有锁,但是其他线程并不能操作堆顶元素
Thread thisThread = Thread.currentThread();
leader = thisThread;
try {
available.awaitNanos(delay);
} finally {
// 第一个短时间挂起的线程释放leader标记
// 这里可以放弃leader标记的原因是已经获取了锁
// 下一次执行一定会成功出队
if (leader == thisThread)
leader = null;
}
}
}
}
} finally {
if (leader == null && q.peek() != null)
available.signal();
lock.unlock();
}
}
3). poll操作
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();
}
}
(3). 小结
内部使用PriorityQueue存放时间,使用ReentrantLock实现线程的同步