jdk.DelayedWorkQueue源码解析
1、概念
DelayedWorkQueue:
延时队列,是jdk的ScheduledThreadPoolExecutor线程池的内部类,其底层通过数组实现的堆来存储任务。
既然DelayedWorkQueue底层是堆结构,那么满足二叉树的一些特性。
堆有小顶堆和大顶堆之分,小顶堆就是父节点比子节点小,大顶堆就是父节点比子节点大。
2、队列继承结构
注:从继承结构发现,DelayedWorkQueue是一个阻塞队列,他满足集合的条件
队列属性:
// 队列数组初始化的长度
private static final int INITIAL_CAPACITY = 16;
// 队列数组(存储的是RunnableScheduledFuture,)
private RunnableScheduledFuture<?>[] queue =
new RunnableScheduledFuture<?>[INITIAL_CAPACITY];
// 锁:插入、移出等都需要加锁
private final ReentrantLock lock = new ReentrantLock();
// 数组的任务数量
private int size = 0;
private Thread leader = null;
private final Condition available = lock.newCondition();
- 队列数组初始化的长度16,当数组元素已满时,按原有长度的二分之一进行扩充
- leader线程用于等待队列头部任务
- queue 队列存储的是ScheduledFutureTask,ScheduledFutureTask继承了RunnableScheduledFuture,RunnableScheduledFuture实现了Runnable
- leader 线程用于等待队列头部任务,当队列根节点为null时,通知其他线程进入等待状态
- available:当线程成为leader时,通知其他线程等待
3、DelayedWorkQueue实现
3.1 新增元素
public boolean offer(Runnable x) {
if (x == null)
throw new NullPointerException();
RunnableScheduledFuture<?> e = (RunnableScheduledFuture<?>)x;
final ReentrantLock lock = this.lock;
lock.lock();
try {
int i = size;
if (i >= queue.length)
grow();
size = i + 1;
if (i == 0) {
queue[0] = e;
setIndex(e, 0);
} else {
siftUp(i, e);
}
if (queue[0] == e) {
leader = null;
available.signal();
}
} finally {
lock.unlock();
}
return true;
}
插入元素流程
1.获取队列长度,判断队列是否存在元素,如果不存在则将元素放在下标为0的数组地址上,
并且将放入的元素的下标设置为0(通过setIndex方法完成),方便以后通过下标查找。
2.如果队列中存在元素,则通过siftUp方法插入到队列中(siftUp方法实现在下面)。
3.如果此次插入的元素在第一个位置,则leader设置为null,并且唤醒其他线程
3.2 siftUp-插入元素
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;
queue[k] = e;
setIndex(e, k);
k = parent;
}
queue[k] = key;
setIndex(key, k);
}
逻辑:
1.k是要插入的下标,获取k的父节点,判断父节点是否比key大,如果比key大,则key和父节点交换位置,
设置父节点的下标,再次循环再次判断是否满足。
2.将key放入下标为k的位置,更新key元素的下标为k,经过此方法处理,堆满足了小顶堆的特点
注:
- compareTo方法比较的是元素任务内任务的执行时间,越小的再队列中排名越靠前
3.2 siftUp-插入元素
private void siftDown(int k, RunnableScheduledFuture<?> key) {
int half = size >>> 1;
while (k < half) {
int child = (k << 1) + 1;
RunnableScheduledFuture<?> c = queue[child];
int right = child + 1;
if (right < size && c.compareTo(queue[right]) > 0)
c = queue[child = right];
if (key.compareTo(c) <= 0)
break;
queue[k] = c;
setIndex(c, k);
k = child;
}
queue[k] = key;
setIndex(key, k);
}
流程:
k是根节点的的下标,key是从二叉树末尾获取,然后放到根节点的任务
1.获取该二叉树的深度half,half就是树的深度,不能超多树的深度
2.获取k节点的左子节点,取出左右子节点中最小的一个,然后跟key进行比较,如果小于key,把字节点上移,
设置子节点的下标,一次循环进行比较,直到找到满足key的位置
3.将key任务放入最终得到的下标下,设置key的下标,siftDown方法:不满足小顶堆的堆进行重新排序,直到满足,
每次遍历都是二分遍历,所以时间复杂度为log n
3.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 = 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 && queue[0] != null)
available.signal();
lock.unlock();
}
}
流程:
1.获取根节点,如果根节点是null,则该线程进入等待状态
2.如果任务的应该执行的时间超过了现在的时间,则调用finishPoll方法取出根节点任务
3.如果还没有到任务执行的时间,则判断当前线程是否是leader线程,如果是则该线程进入等待状态
4.将当前线程设置为leader,然后进入等待状态,等待时间:delay
3.4 finishPoll-取出最后一个节点放到根节点位置
private RunnableScheduledFuture<?> finishPoll(RunnableScheduledFuture<?> f) {
int s = --size;
RunnableScheduledFuture<?> x = queue[s];
queue[s] = null;
if (s != 0)
siftDown(0, x);
setIndex(f, -1);
return f;
}
流程:
1.取出最后一个节点,然后放到根节点位置,最后一个节点设置为null
2.如果x不是根节点的话,重新排序堆
3.将取出的f任务的索引设置为-1