Java 延时队列,简单使用方式

Java 延时队列,简单使用方式

前言:首先我们要知道延时队列是什么?可以用来干什么?

是什么?

看名称 队列可想而知先进先出的一个集合。但我们的延时队列并没有完全的遵循这个理念。

DelayQueue内部其实是基于我们的优先队列来实现的,也就是元素的先后顺序是按元素的Comparable接口提供,这也是顺序来出队的。

DelayQueue内部的元素必须是Delayed的的实现类 而Delayed的父接口是Comparable<Delayed>

Delayed接口中需要我们实现一个long getDelay(TimeUnit unit); 元素的剩余时间 TTL

干什么?

  1. 定时任务。
  2. 重试(5秒重试)。
  3. 延时通知。

都可以基于延时队列来实现。

开发中遇到一个这样的需求。

需要一个集合来维护热点数据,但这个热点数据是有时效性的,我们在去查询的时候先要判断下这个热点数据不存在,或者不在有效期内的。才能走剩下的逻辑。

当然这个需求听起来并不复杂。

我们自己实现的话。

是不是需要一个有顺序的集合?

是不是还每个元素都有一个有效期?

是不是还得保证元素出队的顺序?

如果说还有另外一个地方需要复用,我们是不是还得再写一套这样的逻辑。

所以,我们可以直接使用DelayQueue来实现这个需求,他天生就能保证我们的集合是有顺序的,并且保证过期的元素不在集合内。

简单使用

延时对象


@Data
static class SimpleDelayed<T> implements Delayed {

    /**
     * 对象创建时间
     */
    @JsonIgnore
    private final Date createDate;

    /**
     * 延时数(单位毫秒)
     */
    private long delayCount;

    private T data;

    public SimpleDelayed(T data, long delayCount) {
        this.data = data;
        this.delayCount = delayCount;
        this.createDate = new Date();
    }

    @Override
    public long getDelay(TimeUnit unit) {
        long diff = this.createDate.getTime() + this.delayCount - System.currentTimeMillis();
        return unit.convert(diff, TimeUnit.MILLISECONDS);
    }

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

注意:compareTo提供的顺序必须要与getDelay否则会出现元素过期了但是在队列中。因为DelayQueue内部维护的是一个优先队列。所以必须保证这两顺序是一致的。所以这里的compareTo直接比较了这两的剩余过期数越小的越先出队。反之会出现,延时时间已过,但是没有出队。compareTo靠前,但是过期数未过的数卡着。

延时队列工具类

@Slf4j
public class SimpleDelayQueueUtils {
   /**
     * 1.定义了一个延时队列 队列中元素是我们的SimpleDelayed
     */
    private final static DelayQueue<SimpleDelayed> SIMPLE_DELAYED_DELAY_QUEUE = new DelayQueue<>();

    static{
      	// 2.声明一个异步线程
        CompletableFuture.runAsync(() -> {
          	// 3.通过自旋去出队已过期的元素
            for (;;) {
                try {
					// 4.take() 对头元素出队。
                    log.info("元素已过期{}",SIMPLE_DELAYED_DELAY_QUEUE.take().getData());
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        // 单个线程的线程池,为了不占用fork-join线程池
        }, Executors.newSingleThreadExecutor());
    }

    public static void add(SimpleDelayed... delayeds) {
        SIMPLE_DELAYED_DELAY_QUEUE.addAll(Arrays.asList(delayeds));
    }


  	// 测试
    public static void main(String[] args) {
        SimpleDelayed<String> nonceDelayed = new SimpleDelayQueueUtils.SimpleDelayed<>("111",5001);
        SimpleDelayed<String> nonceDelayed1 = new SimpleDelayQueueUtils.SimpleDelayed<>("222", 4500);
        SimpleDelayQueueUtils.add(nonceDelayed,nonceDelayed1);
    }
}

输出

23:35:00.693 [pool-1-thread-1] INFO com.mfyuan.chenapioperation.util.SimpleDelayQueueUtils - 元素已过期222
23:35:01.189 [pool-1-thread-1] INFO com.mfyuan.chenapioperation.util.SimpleDelayQueueUtils - 元素已过期111

思考

这里的data元素是可以灵活替换的,因为我这里的需求是涉及到队列中的元素是否过期。所以这些就已经足够了。

但是当我们把data,替换成Runable再把出队的的元素通过线程池的方式去调用则就实现了定时任务重试也可以当做定时任务来理解,就把当单做一个任务,如果任务执行失败了或者出现异常了,那么我们重新讲元素放入延时队列中即可。

后话

Redis来实现我这个功能,虽然可以实现但是有些地方实现的逻辑也是不简单。更别说引入redis的开发成本,杀🐔不需要🐂刀。

Redis zset是可以保证集合的元素有顺序的,zsetttl是指这个zset整体的过期时间。(不确定,)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

假女吖☌

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值