DelayedWorkQueue 是一个定制的阻塞队列,专门用来存放RunnableScheduledFutures任务,它是有界的,有最大容量,一般达不到,虽然它是队列,它并不能满足先进先出规则,它是按优先级出队,它会将插入的数据进行排序,按优先级出队,底层采用数组实现的二叉堆,它是为定时线程池服务的,它会根据提交进来的任务的延迟时间进行排序,始终将距离当前时间最近的任务排在前面。具体情况,我们根据源码来解释
1、重要属性
// 队列初始容量
private static final int INITIAL_CAPACITY = 16;
// 存放任务的数组
private RunnableScheduledFuture<?>[] queue =
new RunnableScheduledFuture<?>[INITIAL_CAPACITY];
// 锁
private final ReentrantLock lock = new ReentrantLock();
// 元素个数
private int size = 0;
// leader一般会存储等待堆顶数据的消费者
private Thread leader = null;
// 等待队列
private final Condition available = lock.newCondition();
2、重要方法
2.1 写入方法
2.1.1 add方法
public boolean add(Runnable e) {
return offer(e);
}
2.1.2 put方法
public void put(Runnable e) {
offer(e);
}
2.1.3 offer方法
// offer方法向队列中添加元素
// e 需要添加到队列的数据
// timeout 等待时间
// unit 时间单位
public boolean offer(Runnable e, long timeout, TimeUnit unit) {
return offer(e);
}
public boolean offer(Runnable x) {
if (x == null)
throw new NullPointerException();
// 强转
RunnableScheduledFuture<?> e = (RunnableScheduledFuture<?>)x;
// 加锁
final ReentrantLock lock = this.lock;
lock.lock();
try {
// 将size赋值给i,size是当前队列中的元素个数
int i = size;
// 队列满了,就进行扩容
if (i >= queue.length)
grow();
// size+1
size = i + 1;
// 如果队列中没有元素,默认放在第0个位置
if (i == 0) {
queue[0] = e;
setIndex(e, 0);
} else {
// 通过siftUp,比较数据大小,存储数据,并根据优先级排序规则进行移动,保证二叉树平衡
siftUp(i, e);
}
// 队列头部元素变成了当前的e,则需要将leader设置为null,因为leader等待的是之前的队头数据
// 唤醒等待队列中的消费者,来等待当前的队头数据
if (queue[0] == e) {
leader = null;
available.signal();
}
} finally {
// 释放锁
lock.unlock();
}
return true;
}
// 扩容
private void grow() {
// 数组当前容量
int oldCapacity = queue.length;
// 扩容到原数组的1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1); // grow 50%
// newCapacity变为负数,长度已经大于MAX_VALUE了,则取MAX_VALUE。
if (newCapacity < 0) // overflow
newCapacity = Integer.MAX_VALUE;
// 将当前数组拷贝到新数组
queue = Arrays.copyOf(queue, newCapacity);
}
// 存储数据,并根据优先级排序规则进行移动,保证二叉树平衡
// k是当前key要存放的位置,也就是队尾,但是要保证最小堆原值,还需要进行调整
private void siftUp(int k, RunnableScheduledFuture<?> key) {
while (k > 0) {
// 找到父节点的位置
int parent = (k - 1) >>> 1;
// 取出父元素
RunnableScheduledFuture<?> e = queue[parent];
// 如果当前加入队列的元素比父节点大,则直接放
if (key.compareTo(e) >= 0)
break;
// 如果加入的元素比父节点小,则将父节点放到k位置,也就是父节点往下移动
queue[k] = e;
setIndex(e, k);
// 重新设置x节点需要放置的位置。k就来到了父节点原本的位置,此时再进行while判断,
// 如果合适则放下,不合适继续重复刚才的操作,直到找到合适的位置,或者找到根节点
k = parent;
}
// 将key放在找到的k位置
queue[k] = key;
setIndex(key, k);
}
2.2 读取操作
2.2.1 peek方法
查看堆顶元素
public RunnableScheduledFuture<?> peek() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
// 直接返回堆顶元素
return queue[0];
} finally {
lock.unlock();
}
}
2.2.2 poll方法
不阻塞的poll,取出堆顶元素,如果堆顶元素还没到出队时间或者堆顶元素为空,直接返回null,否则返回堆顶元素
阻塞的poll,等待阻塞时间,指定时间内拿到元素返回,拿不到元素,时间到了返回null
public RunnableScheduledFuture<?> poll() {
// 加锁
final ReentrantLock lock = this.lock;
lock.lock();
try {
// 获取堆顶元素
RunnableScheduledFuture<?> first = queue[0];
// 堆顶元素为空或者还没到出队时间 返回null
if (first == null || first.getDelay(NANOSECONDS) > 0)
return null;
else
// 将最后一个元素放到堆顶,并向下堆化(最后一个元素的延迟时间肯定是最大的,
// 将它放到堆顶之后,需要重新给他找位置)
return finishPoll(first);
} finally {
// 释放锁
lock.unlock();
}
}
private RunnableScheduledFuture<?> finishPoll(RunnableScheduledFuture<?> f) {
// 元素个数减1
int s = --size;
// 取出最后一个元素
RunnableScheduledFuture<?> x = queue[s];
// 将最后一个位置置为null
queue[s] = null;
// 如果s== 0 说明已经是最后一个元素,不需要保证堆结构
if (s != 0)
// 由于堆顶元素出队列后,就破坏了堆的结构,需要组织整理,将堆尾元素移到堆顶,然后向下堆化
siftDown(0, x);
// 出堆后,将index置为-1
setIndex(f, -1);
return f;
}
// key堆底元素
// k堆底元素放置的位置(默认为0)
private void siftDown(int k, RunnableScheduledFuture<?> key) {
// 无符号右移,相当于size/2
// // 因为二叉堆是一个二叉满树,所以在保证二叉堆结构时,只需要做一半就可以
int half = size >>> 1;
// 做了超过一半,就不需要再往下找了。
while (k < half) {
// 寻找子节点
int child = (k << 1) + 1;
// 左子节点
RunnableScheduledFuture<?> c = queue[child];
// 又子节点的位置
int right = child + 1;
// right < size 保证右子节点存在
// c.compareTo(queue[right]) > 0 左子节点比右子节点大
if (right < size && c.compareTo(queue[right]) > 0)
// 取出较小的子节点
c = queue[child = right];
// 如果堆尾元素比当前较小的子节点小 则不用继续寻找,跳出循环
if (key.compareTo(c) <= 0)
break;
// 否则将较小的子节点c向上提,放到堆顶
queue[k] = c;
setIndex(c, k);
k = child;
}
// 最终将key放到找到的k位置
queue[k] = key;
setIndex(key, k);
}
public RunnableScheduledFuture<?> poll(long timeout, TimeUnit unit)
throws InterruptedException {
long nanos = unit.toNanos(timeout);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
for (;;) {
RunnableScheduledFuture<?> first = queue[0];
if (first == null) {
if (nanos <= 0)
return null;
else
nanos = available.awaitNanos(nanos);
} else {
long delay = first.getDelay(NANOSECONDS);
if (delay <= 0)
return finishPoll(first);
if (nanos <= 0)
return null;
first = null; // don't retain ref while waiting
if (nanos < delay || leader != null)
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 && queue[0] != null)
available.signal();
lock.unlock();
}
}
2.2.3 take方法
如果取不到元素就死等,直到取到元素为止
public RunnableScheduledFuture<?> take() throws InterruptedException {
// 加锁
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
for (;;) {
// 堆顶元素为空,当前读线程阻塞
RunnableScheduledFuture<?> first = queue[0];
if (first == null)
available.await();
else {
// 取出堆顶元素的出堆时间
long delay = first.getDelay(NANOSECONDS);
if (delay <= 0)
// 时间已到 执行出堆
return finishPoll(first);
// 将first置空,线程等待时,不持有堆顶元素的引用(当线程醒来时,堆顶元素可能已经变化)
first = null; // don't retain ref while waiting
// leader为当前正在等待堆顶元素的线程,如果已经有人在等堆顶元素,那么当前线程阻塞
if (leader != null)
available.await();
else {
// 走到这里,说明当前消费者的阻塞时间可以拿到数据,并且没有其他消费者在等待堆顶数据
// 将leader设置为当前线程
Thread thisThread = Thread.currentThread();
leader = thisThread;
try {
// 阻塞任务的延迟时间 当任务的延时时间到了时,能够自动超时唤醒
available.awaitNanos(delay);
} finally {
// 将如果leader是当前线程,置为空
if (leader == thisThread)
leader = null;
}
}
}
}
} finally {
// 没有消费者在等待元素,队列中的元素不为null
if (leader == null && queue[0] != null)
// 只要当前没有leader在等,并且队列有元素,就需要再次唤醒消费者。、
// 避免队列有元素,但是没有消费者处理的问题
available.signal();
// 释放锁
lock.unlock();
}
}