DelayQueue 源码

调用 demo

java 复制代码
import java.util.concurrent.DelayQueue;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;

class Task implements Delayed {
    private long expire;
    private String name;

    public Task(String name, long delay, TimeUnit unit) {
        this.name = name;
        this.expire = System.currentTimeMillis() + unit.toMillis(delay);
    }

    @Override
    public long getDelay(TimeUnit unit) {
        long remain = expire - System.currentTimeMillis();
        return unit.convert(remain, TimeUnit.MILLISECONDS);
    }

    @Override
    public int compareTo(Delayed o) {
        return Long.compare(this.getDelay(TimeUnit.MILLISECONDS), o.getDelay(TimeUnit.MILLISECONDS));
    }

    @Override
    public String toString() { return name; }
}

public class DelayQueueMain {
    public static void main(String[] args) throws InterruptedException {
        DelayQueue<Task> queue = new DelayQueue<>();
        queue.put(new Task("1s任务", 1, TimeUnit.SECONDS));
        queue.put(new Task("3s任务", 3, TimeUnit.SECONDS));
        queue.put(new Task("2s任务", 2, TimeUnit.SECONDS));

        for (int i = 0; i < 3; i++) {
            Task t = queue.take();
            // 取出到期:1s任务
			// 取出到期:2s任务
			// 取出到期:3s任务
            System.out.println("取出到期:" + t);
        }
    }
}

成员变量

java 复制代码
// 底层最小堆,存储延迟元素
private final PriorityQueue<E> q = new PriorityQueue<E>();

// 全局独占锁,所有读写加锁
private final ReentrantLock lock = new ReentrantLock();

// 等待条件:无到期元素/队空时阻塞take线程
private final Condition available = lock.newCondition();

// Leader-Follower 优化标记
private Thread leader = null;

构造方法

java 复制代码
// ========== 无参构造器 main new 时执行 ==========
public DelayQueue() {}

// 带集合构造器(main未使用,附原版)
public DelayQueue(Collection<? extends E> c) {
    this.addAll(c);
}

入队方法

put

java 复制代码
public void put(E e) {
    offer(e);
}

add

java 复制代码
public boolean add(E e) {
    return offer(e);
}

offer(超时)

java 复制代码
public boolean offer(E e, long timeout, TimeUnit unit) {
    return offer(e);
}

offer

java 复制代码
public boolean offer(E e) {
     final ReentrantLock lock = this.lock;
     lock.lock();
     try {
         q.offer(e); // 放入堆,自动上浮排序
         // 如果新元素是堆顶,清空 leader 并唤醒 1 个线程
         if (q.peek() == e) {
             leader = null;
             available.signal();
         }
         return true;
     } finally {
         lock.unlock();
     }
}

出队方法

take

java 复制代码
public E take() throws InterruptedException {
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly(); // 可中断锁,响应线程interrupt
    try {
        for (;;) { // 死循环自旋校验队首
            E first = q.peek();
            // 分支1:队列为空,无限阻塞等待
            if (first == null)
                available.await();
            else {
                long delay = first.getDelay(NANOSECONDS);
                // 分支2:队首已到期,直接弹出返回
                if (delay <= 0)
                    return q.poll();
                // 未到期,释放引用防止内存泄漏
                first = null;
                // 分支3:已有 leader 线程,当前线程永久阻塞
                if (leader != null)
                    available.await();
                else {
                    // 无leader,当前线程成为 leader,定时等待剩余延迟
                    Thread thisThread = Thread.currentThread();
                    leader = thisThread;
                    try {
                        available.awaitNanos(delay);
                    } finally {
                        // 等待结束,清空leader标记
                        if (leader == thisThread)
                            leader = null;
                    }
                }
            }
        }
    } finally {
        // 释放锁前:如果无 leader 且队列有元素,唤醒一个等待线程
        if (leader == null && q.peek() != null)
            available.signal();
        lock.unlock();
    }
}

poll

java 复制代码
public E poll() {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        E first = q.peek();
        // 空 或 未到期 返回null
        if (first == null || first.getDelay(NANOSECONDS) > 0)
            return null;
        else
            return q.poll();
    } finally {
        lock.unlock();
    }
}

总结

  1. 无界阻塞队列:put/offer 永不阻塞,无容量限制
  2. 底层最小堆:队首一定是最快到期元素
  3. 线程安全:全局一把 ReentrantLock ,所有操作都会上锁
  4. leader 等待优化:减少多线程定时竞争,提升并发性能
  5. 仅到期可出队:take/poll 不会返回未到期元素,未到期线程阻塞

相关考点

first = null 为什么可以防止内存泄漏

不执行 first = null 时的引用关系如下:

  • 阻塞线程的局部变量 first 引用堆顶延迟任务对象 Delayed Task
  • DelayQueue 内部 PriorityQueue 也引用同一个 Task

当线程执行到 available.await() / awaitNanos() 阻塞时:

线程挂起、长时间休眠,线程栈帧不会销毁,局部变量 first 会一直存活

双重强引用导致 GC 无法回收

队列里本来就有一份引用,再加线程局部变量的强引用

当这个任务到期被别的线程 poll 取出、从堆里删掉,当前阻塞线程的变量 first 依旧死死持有该对象

只要线程没被唤醒、栈帧没退出,Task 及其关联大内存就无法被 GC,长时间大量阻塞线程会造成内存泄漏

相关推荐
运行时异常几秒前
【WMS 仓储系统集成 AI Agent 实战】第 7 讲:Vue3 前端工程化——Token 刷新锁、Markdown 渲染踩坑与权限体系
java
她的男孩1 分钟前
开放接口限流从 20 改到 200 还是每分钟 20 次:拆完防重放+幂等+限流,我找到 5 个静默失效的坑
java·后端·架构
天天被压力7 分钟前
【Python 量化取数指南 #13】Python 把行情落库:sqlite 一键存,回测随用随取
java·人工智能·python
天天被压力7 分钟前
【Python 量化取数指南 #14】Python 清洗行情数据:复权停牌对齐,回测不翻车
java·人工智能·python
独泪了无痕11 分钟前
开发利器Hutool之MapUtil的使用
java·后端
唐青枫15 分钟前
Java ThreadLocal 详解:从线程隔离到线程池实战,彻底避开脏数据与内存泄漏
java
T_Apollo15 分钟前
2.6 Java 11 版本新增特性和类
java
用户37215742613524 分钟前
如何使用 Java 创建 Excel 数据透视表
java
codigger28 分钟前
ObjectSense:一门千行内核、把可靠性写进骨子里的面向对象脚本语言
开发语言·编程·编程语言
用户31268748772028 分钟前
JVM 内存结构全链路:堆、栈、方法区、元空间
java