Delayed + DelayQueue 实现延迟任务

132 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第8天,点击查看活动详情

Java 单机延迟任务我们可以通过, Delayed + DelayQueue 实现

实现 Delayed 接口

实现 Delayed 接口 ,自定义消息体/延迟任务对象

  • activeTime 是延迟任务到期时间,单位 ms
  • data 是一个业务实体,泛型结构,我们可以自定义结构体,或者类型。
  • compareTo() 方法是按照执行时间排序
  • getDelay() 获取到期时间
public class DelayMessage<T> implements Delayed {
    /**
     * 到期时间 单位:ms
     */
    private long activeTime;
    /**
     * 业务实体
     */
    private T data;

    public DelayMessage(long activeTime, T data) {
        // 将传入的时间转换为超时的时刻
        this.activeTime = TimeUnit.NANOSECONDS.convert(activeTime, TimeUnit.MILLISECONDS)
                + System.nanoTime();
        this.data = data;
    }

    public long getActiveTime() {
        return activeTime;
    }

    public T getData() {
        return data;
    }

    /**
     * 按照剩余时间进行排序
     *
     * @param o
     * @return
     */
    @Override
    public int compareTo(Delayed o) {
        // 订单剩余时间-当前传入的时间= 实际剩余时间(单位纳秒)
        long d = getDelay(TimeUnit.NANOSECONDS) - o.getDelay(TimeUnit.NANOSECONDS);
        // 根据剩余时间判断等于0 返回1 不等于0
        // 有可能大于0 有可能小于0  大于0返回1  小于返回-1
        return (d == 0) ? 0 : ((d > 0) ? 1 : -1);
    }

    /**
     * 获取剩余时间
     *
     * @param unit
     * @return
     */
    @Override
    public long getDelay(TimeUnit unit) {
        // 剩余时间= 到期时间-当前系统时间,系统一般是纳秒级的,所以这里做一次转换
        return unit.convert(activeTime - System.nanoTime(), TimeUnit.NANOSECONDS);
    }
}

创建任务任务执行方法

创建任务执行线程、方法,去调用 delayQueue获取延迟任务对象。 通常情况下我们可以定义一个独立的线程去处理这些任务。我们可以使用 Jdk8 的拉姆达表达式来实现线程的包装。

/**
 * 延时任务执行线程
 */
private void executeThread() {
    while (true) {
        DelayMessage<Object> task = null;
        try {
            task = delayQueue.take();
            processTask(task);
        } catch (Exception e) {
            System.err.println("延时任务执行失败 task:" + task + " err:" + e);
        }
    }
}

/**
 * 内部执行延时任务
 *
 * @param task
 */
private void processTask(DelayMessage<Object> task) {
    if (task == null) {
        return;
    }
    Object data = task.getData();
    System.out.println("out:" + data + " run time:" + System.currentTimeMillis());
}

管理 DelayQueue 任务

DelayQueueManager 类主要是用来管理 DelayQueue 到期任务的执行,我们在构造方法中创建了一个子线程对 delayQueue.take(); 进行轮询获取到期任务,如果存在到期任务就进行执行:

PS:当前实验代码只适用于小任务量的场景,如果我们的任务比较多的话,我们可以把逻辑执行的方法换成一个线程提交逻辑,进行异步处理,防止 take 线程阻塞。

public class DelayQueueManager {

    private DelayQueue<DelayMessage<Object>> delayQueue = new DelayQueue<>();

    private static DelayQueueManager instance = new DelayQueueManager();

    public static DelayQueueManager getInstance() {
        return instance;
    }

    private DelayQueueManager() {
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        executorService.execute(new Thread(this::executeThread));
    }
    
    /**
     * 加入到延时队列中
     *
     * @param task
     */
    public void put(DelayMessage<Object> task) {
        System.out.println("加入延时任务 delay= " + task.getDelay(TimeUnit.MILLISECONDS) + "ms");
        delayQueue.put(task);
    }

    /**
     * 延时任务执行线程
     */
    private void executeThread() {
        while (true) {
            DelayMessage<Object> task = null;
            try {
                task = delayQueue.take();
                processTask(task);
            } catch (Exception e) {
                System.err.println("延时任务执行失败 task:" + task + " err:" + e);
            }
        }
    }

    /**
     * 内部执行延时任务
     *
     * @param task
     */
    private void processTask(DelayMessage<Object> task) {
        if (task == null) {
            return;
        }
        Object data = task.getData();
        System.out.println("out:" + data + " run time:" + System.currentTimeMillis());
    }
}

测试程序

下面是一个简单的测试代码,我们提交三个延迟消息。然后在观察我们的打印信息.

测试代码如下:

public static void main(String[] args) {
    DelayQueueManager delayQueueManager = new DelayQueueManager();
    System.out.println("start " + System.currentTimeMillis());
    delayQueueManager.put(new DelayMessage<>(5000L, "Hello Tom"));
    delayQueueManager.put(new DelayMessage<>(8000L, "Hello Ktm"));
    delayQueueManager.put(new DelayMessage<>(2000L, "Hello Star"));
}

输出结果如下: