jdk.DelayedWorkQueue源码解析

本文详细介绍了JDK中的DelayedWorkQueue,它是ScheduledThreadPoolExecutor线程池的内部类,负责存储延迟执行的任务。文章解释了DelayedWorkQueue的实现原理,包括其小顶堆结构、队列操作方法如offer和take等。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值