延迟队列DelayQueue

1.什么是DelayQueue(延时队列)

        DelayQueue 是一个通过PriorityBlockingQueue实现延迟获取元素的无界队列无界阻塞队列,其中添加进该队列的元素必须实现Delayed接口(指定延迟时间),而且只有在延迟期满后才能从中提取元素。

        为了更好理解,给大家说一个实际场景;

        我们在购物平台买商品的时候,下单后,如果在30min内没有付款就会取消订单,这个就用到了延时队列;

        DelayQueue主要用于两个方面:清掉缓存中超时的缓存数据- 任务超时处理

2.DelayQueue主要方法

1.offer添加元素

public boolean offer(E e) {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        //调用优先队列
        q.offer(e);
        //检验元素是否为队首,是则设置 leader 为null, 并唤醒一个消费线程  
        if (q.peek() == e) {
            leader = null;
            available.signal();
        }
        return true;
    } finally {
        lock.unlock();
    }
}

也可以使用 put() add() 方法 但他们还是调用了offer方法

2.take获取元素

public E take() throws InterruptedException {
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {
        for (;;) {
            //从优先队列中获取第一个元素,peek方法不会删除元素
            E first = q.peek();
            //如果获取不到数据,则调用available.await()进入阻塞状态
            if (first == null)
                available.await();
            else {
                //获取当前延时对象是否到期
                long delay = first.getDelay(NANOSECONDS);
                //到期那么返回这个延时对象
                if (delay <= 0)
                    return q.poll();
                first = null; // 
                //leader不为空,表明已经有其他线程在等待这个延时对象了
                //为什么不available.awaitNanos(delay)呢?这将会导致大量的线程在同一时间点被唤醒,然后去竞争
                //这个到期的延时任务,影响性能,还不如直接将他们无时间限制的wait,leader线程或者其他新进来的线程获取到延时对象后,去唤醒
                //让他们去竞争下一个延时对象
                if (leader != null)
                    available.await();
                else {
                    Thread thisThread = Thread.currentThread();
                    leader = thisThread;
                    try {
                        //指定纳秒级别线程阻塞时间,当前wait住的线程被唤醒后有可能与其他线程竞争失败,就会进入了同步队列阻塞,那个抢到锁的线程就会取走这个延时对象
                        available.awaitNanos(delay);
                    } finally {
                        //leader线程被唤醒并获取到锁之后会将leader设置为空
                        if (leader == thisThread)
                            leader = null;
                    }
                }
            }
        }
    } finally {
        //leader为空并且队列不为空,那么唤醒正在等待的线程
        if (leader == null && q.peek() != null)
            available.signal();
        lock.unlock();  //释放锁
    }
}

        从优先队列中取值,如果取到的延时节点已经已经到期,那么直接返回,如果还没有到期并且已经有其他线程在执行delay时间等待了(也就是leader线程),那么挂起自己(避免延时 相同时间造成大量线程同时唤醒), leader线程在指定delay时间后主动唤醒,然后取竞争锁,如果竞争成功,那么很大概率可以获取到延时节点,如果竞争失败,将被阻塞。

 3.remove删除元素

public boolean remove(Object o) {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        return q.remove(o);
    } finally {
        lock.unlock();
    }
}

3.使用实例

实现Delayed接口:

public class MyDelay<T> implements Delayed {

    long delayTime; //延时时间
    long expire; //过期时间
    T data;

    public MyDelay(long delayTime, T data) {
        this.delayTime = delayTime;
        this.expire = System.currentTimeMillis() + delayTime;
        this.data = data;
    }

    /**
     * 剩余时间 = 到期时间 - 当前时间
     */
    @Override
    public long getDelay(TimeUnit unit) {
        return unit.convert(this.expire - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
    }

    /**
     * 优先级规则 两个任务比较 , 时间短的优先执行
     * @param o
     * @return
     */
    @Override
    public int compareTo(Delayed o) {
        long f = this.getDelay(TimeUnit.MILLISECONDS) - o.getDelay(TimeUnit.MILLISECONDS);
        return (int)f;
    }

    @Override
    public String toString() {
        return "delayTime=" + delayTime +
                ", expire=" + expire +
                ", data=" + data;
    }


}

测试用例如下:

public class DelayQueueDemo {


    static BlockingQueue<Delayed> queue = new DelayQueue();

    public static void main(String[] args) throws InterruptedException {
        queue.add(new MyDelay(800, "第一次添加任务"));
        queue.add(new MyDelay(300, "第二次添加任务"));
        queue.add(new MyDelay(50, "第三次添加任务"));

        while (!queue.isEmpty()) {
            Delayed delayed = queue.take();
            System.out.println(delayed);
        }
    }

}

输出如下:

delayTime=50, expire=1673419474898, data=第三次添加任务
delayTime=300, expire=1673419475148, data=第二次添加任务
delayTime=800, expire=1673419475648, data=第一次添加任务

4.总结

DelayQueue其实采用了装饰器模式,在对PriorityQueue进行包装下增加了延时时间获取元素的功能,其主要特点归纳如下:

1.DelayQueue是一个无界阻塞队列,队列内部使用PriorityQueue来实现。

2.进入队列的元素必须实现Delayed接口,在创建元素时可以指定多久才能从队列中获取当前元素,只有在延迟期满时才能从中提取元素;

3.该队列头部是延迟期满后保存时间最长的Delayed元素;

4.如果没有延迟未过期元素,且队列没有头部,并且poll将返回null;

5.当一个元素的getDelay(TimeUnit.NANOSECONDS)方法返回一个小于等于0的值时,表示该元素已过期;

6.无法使用poll或take移除未到期的元素,也不会将这些元素作为正常元素对待;例如:size方法返回到期和未到期元素的计数之和。

7.此队列不允许使用null元素。
 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
延迟队列DelayQueue)是 Java 并发包中提供的一种队列实现,用于存储具有延迟时间的元素。延迟队列中的元素只有在其指定的延迟时间过去后才能被取出。 下面是一个简单的延迟队列的 Java 实现示例: 首先,我们需要定义一个实现了 Delayed 接口的延迟元素类: ```java import java.util.concurrent.Delayed; import java.util.concurrent.TimeUnit; public class DelayedElement implements Delayed { private String data; private long startTime; public DelayedElement(String data, long delay) { this.data = data; this.startTime = System.currentTimeMillis() + delay; } @Override public long getDelay(TimeUnit unit) { long diff = startTime - System.currentTimeMillis(); return unit.convert(diff, TimeUnit.MILLISECONDS); } @Override public int compareTo(Delayed other) { if (this.startTime < ((DelayedElement) other).startTime) { return -1; } if (this.startTime > ((DelayedElement) other).startTime) { return 1; } return 0; } // Getter and Setter methods } ``` 然后,我们可以使用 DelayQueue 来存储延迟元素,并在需要时取出元素: ```java import java.util.concurrent.DelayQueue; public class DelayQueueExample { public static void main(String[] args) throws InterruptedException { DelayQueue<DelayedElement> delayQueue = new DelayQueue<>(); // 添加延迟元素到队列 delayQueue.add(new DelayedElement("Element 1", 2000)); // 延迟 2 秒 delayQueue.add(new DelayedElement("Element 2", 5000)); // 延迟 5 秒 delayQueue.add(new DelayedElement("Element 3", 1000)); // 延迟 1 秒 // 取出延迟元素并处理 while (!delayQueue.isEmpty()) { DelayedElement element = delayQueue.take(); System.out.println("Processing element: " + element.getData()); } } } ``` 在上面的示例中,我们创建了一个 DelayQueue 对象,并向其中添加了三个延迟元素。然后,我们使用 `take()` 方法从队列中取出元素,并进行处理。如果队列为空,`take()` 方法会阻塞等待,直到有延迟元素可用。 请注意,延迟时间的单位可以根据需要进行调整,例如使用 `TimeUnit.SECONDS` 表示秒。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值