JDK自带的延迟队列——DelayQueue

DelayQueue是基于阻塞队列实现的,是JDK自带延迟队列。这种方案使用成本最低,而且不依赖任何第三方服务,减少了网络交互。但缺点也很明显,就是需要占用JVM内存,在数据量非常大的情况下可能会有问题。如果数据量非常大,DelayQueue不能满足业务需求,也可以替换为其它延迟队列方式,例如Redisson、MQ等。

1. DelayQueue的原理

首先来看一下DelayQueue的源码:

public class DelayQueue<E extends Delayed> extends AbstractQueue<E>​
    implements BlockingQueue<E> {​
​
    private final transient ReentrantLock lock = new ReentrantLock();​
    private final PriorityQueue<E> q = new PriorityQueue<E>();​
    ​
    // ... 略​
}

可以看到DelayQueue实现了BlockingQueue接口,是一个阻塞队列。队列就是容器,用来存储东西的。DelayQueue叫做延迟队列,其中存储的就是延迟执行的任务

我们可以看到DelayQueue的泛型定义:

DelayQueue<E extends Delayed>

这说明存入 DelayQueue 内部的元素必须是 Delayed 类型,这其实就是一个延迟任务的规范接口。来看一下:

public interface Delayed extends Comparable<Delayed> {

    /**
     * Returns the remaining delay associated with this object, in the
     * given time unit.
     *
     * @param unit the time unit
     * @return the remaining delay; zero or negative values indicate
     * that the delay has already elapsed
     */
    long getDelay(TimeUnit unit);
}

从源码中可以看出,Delayed类型必须具备两个方法:

  • getDelay() : 获取延迟任务的剩余延迟时间
  • compareTo( T t ) : 比较两个延迟任务的延迟时间,判断执行顺序

可见,Delayed类型的延迟任务具备两个功能:获取剩余延迟时间、比较执行顺序。当然,我们可以对Delayed做实现和功能扩展,比如添加延迟任务的数据。

将来每一次提交数据,就可以将数据保存在这样的一个 Delayed 类型的延迟任务里并设定延迟时间。然后交给 DelayQueue 队列。 DelayQueue 会调用compareTo方法,根据剩余延迟时间对任务排序。剩余延迟时间越短的越靠近队首,这样就会被优先执行。

2. DelayQueue 的基本使用

2.1 创建 Delayed 的实现类

import lombok.Data;

import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;

@Data
public class DelayedTask implements Delayed {
    // 消息体
    private Object data;
    // 过期时间
    private Long deadTime;

    /**
     * 到期时间,毫秒
     */
    public DelayedTask(Object data, Long expireTime) {
        this.data = data;
        this.deadTime = System.currentTimeMillis() + expireTime * 1000;
    }

    /**
     * 获取元素的剩余的有效期
     */
    @Override
    public long getDelay(TimeUnit unit) {
        long remain = this.deadTime - System.currentTimeMillis();
        if (remain < 0) {
            remain = 0;
        }
        return unit.convert(remain, TimeUnit.MILLISECONDS);
    }

    /**
     * 比较元素的剩余,看谁先到期
     */
    @Override
    public int compareTo(Delayed o) {
        long l = this.getDelay(TimeUnit.MILLISECONDS) - o.getDelay(TimeUnit.MILLISECONDS);
        if (l > 0) {
            return 1;
        } else if (l < 0) {
            return -1;
        } else {
            return 0;
        }
    }
}

2.2 向列队发送消息并接收

import com.example.delayedqueuedemo.task.DelayedTask;
import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.DelayQueue;

@Slf4j
public class DelayedQueueDemo {
    public static void main(String[] args) throws InterruptedException {
        // 1.初始化延迟队列
        DelayQueue<DelayedTask> delayQueue = new DelayQueue<>();
        DelayedTask task1 = new DelayedTask("data 5 seconds", 5L);
        DelayedTask task2 = new DelayedTask("data 1 seconds", 1L);
        DelayedTask task3 = new DelayedTask("data 3 seconds", 3L);
        // 2.向队列中添加延迟执行的任务
        delayQueue.put(task1);
        delayQueue.put(task2);
        delayQueue.put(task3);
        // 3.尝试执行任务
        log.info("消息发送完成");
        // 如果队列中没有到期的元素,take会阻塞等待
        while (true) {
            DelayedTask task = delayQueue.take();
            log.info("获取延迟任务:task:{}", task.getData());
        }
    }
}

3. 实际项目应用

3.1 将发送端和接收端分离

在实际项目中,需要将发送端和接收端分离,避免死循环的队列接收阻塞主程序运行,故将列队接收代码抽离到 Spring 在生命周期回调期间执行,具体代码如下:

import com.example.delayedqueuedemo.task.DelayedTask;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

@Slf4j
@Service
public class DelayedService {
    private ThreadPoolExecutor takeTaskPool = new ThreadPoolExecutor(1, 1, 120, TimeUnit.SECONDS, new ArrayBlockingQueue<>(1));
    private ThreadPoolExecutor executeTaskPool = new ThreadPoolExecutor(Runtime.getRuntime().availableProcessors(), Runtime.getRuntime().availableProcessors(), 120, TimeUnit.SECONDS, new ArrayBlockingQueue<>(Runtime.getRuntime().availableProcessors()), new ThreadPoolExecutor.CallerRunsPolicy());
    private DelayQueue<DelayedTask> delayQueue = new DelayQueue<>();

    /**
     * 生命周期回调
     */
    @PostConstruct
    public void postConstruct() {
        // 开启线程池,避免死循环阻塞主程序
        takeTaskPool.execute(new Runnable() {
            @Override
            public void run() {
                // 如果队列中没有到期的元素,take会阻塞等待
                while (true) {
                    try {
                        DelayedTask task = delayQueue.take();
                        // 为了防止业务逻辑执行太长,阻塞消息的读取
                        executeTaskPool.execute(new Runnable() {
                            @Override
                            public void run() {
                                // 执行业务代码
                                log.info("获取延迟任务:task:{}", task.getData());
                            }
                        });
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
    }

    public void delayed(String data, Long expireTime) {
        // 1.初始化延迟队列
        DelayedTask task = new DelayedTask(data, expireTime);
        // 2.向队列中添加延迟执行的任务
        delayQueue.put(task);
        // 3.尝试执行任务
        log.info("消息发送完成");
    }
}

3.2 开启事务

若需要开启事务支持,直接用 @Transactional 注解会出现事务失效的情况(@Transactional 失效场景可参考该文章: 一口气怼完12种@Transactional的失效场景 - 知乎 ),故有两种方法解决:

  1. 将业务代码写到新的 service 服务中,让 Spring 代理;
  2. 编程式事务处理

以下代码以编程式事务处理方案演示如何开启事务:

import com.example.delayedqueuedemo.task.DelayedTask;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionException;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.DefaultTransactionDefinition;

import javax.annotation.PostConstruct;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

@Slf4j
@Service
public class DelayedService {
    private ThreadPoolExecutor takeTaskPool = new ThreadPoolExecutor(1, 1, 120, TimeUnit.SECONDS, new ArrayBlockingQueue<>(1));
    private ThreadPoolExecutor executeTaskPool = new ThreadPoolExecutor(Runtime.getRuntime().availableProcessors(), Runtime.getRuntime().availableProcessors(), 120, TimeUnit.SECONDS, new ArrayBlockingQueue<>(Runtime.getRuntime().availableProcessors()), new ThreadPoolExecutor.CallerRunsPolicy());
    private DelayQueue<DelayedTask> delayQueue = new DelayQueue<>();
    @Autowired
    private PlatformTransactionManager transactionManager;

    /**
     * 生命周期回调
     */
    @PostConstruct
    public void postConstruct() {
        // 开启线程池,避免死循环阻塞主程序
        takeTaskPool.execute(new Runnable() {
            @Override
            public void run() {
                // 如果队列中没有到期的元素,take会阻塞等待
                while (true) {
                    try {
                        DelayedTask task = delayQueue.take();
                        // 开启线程池,避免业务执行阻塞队列获取消息,核心线程数根据电脑cpu核数而定
                        executeTaskPool.execute(new Runnable() {
                            @Override
                            public void run() {
                                // 开启事务
                                TransactionDefinition definition = new DefaultTransactionDefinition();
                                TransactionStatus transaction = transactionManager.getTransaction(definition);
                                try {
                                    // 业务逻辑
                                    log.info("获取延迟任务:task:{}", task.getData());
                                    // 提交事务
                                    transactionManager.commit(transaction);
                                } catch (TransactionException e) {
                                    // 回滚事务
                                    transactionManager.rollback(transaction);
                                }
                            }
                        });
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
    }

    @Transactional
    public void delayed(String data, Long expireTime) {
        // 1.初始化延迟队列
        DelayedTask task = new DelayedTask(data, expireTime);
        // 2.向队列中添加延迟执行的任务
        delayQueue.put(task);
        // 3.尝试执行任务
        log.info("消息发送完成");
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值