延迟队列 DelayQueue讲解
前言
在项目中,下单业务下单之后,如果30分钟之内没有付款就自动取消付款,就需要用到延迟队列,来实现取消功能
一、DelayQueue是什么?
DelayQueue 是一个无界的 BlockingQueue 队列,用于放置实现了Delayed接口的对象,其中的对象只能在其到期时才能从队列中取走。这种队列是有序的(延迟时间排序),即队头对象的延迟到期时间最长。 不能放置null元素。
二、使用步骤
1.创建一个Delayed接口的实现类,该元素要放入到DealyQueue队列中
代码如下(示例):
import com.google.common.primitives.Ints;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;
public abstract class Task implements Delayed, Runnable{
private String id = "";
private long start = 0;
public Task(String id, long delayInMilliseconds){
this.id = id;
this.start = System.currentTimeMillis() + delayInMilliseconds;
}
public String getId() {
return id;
}
//获取剩余的时间,为0获取负数时取出
//TimeUnit.NANOSECONDS 毫微妙
@Override
public long getDelay(TimeUnit unit) {
long diff = this.start - System.currentTimeMillis();
return unit.convert(diff, TimeUnit.MILLISECONDS);
}
// 排序
@Override
public int compareTo(Delayed o) {
return Ints.saturatedCast(this.start - ((Task) o).start);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null) return false;
if (!(o instanceof Task)) {
return false;
}
Task t = (Task)o;
return this.id.equals(t.getId());
}
@Override
public int hashCode() {
return this.id.hashCode();
}
}
2.创建DelayQueue 的工具类
代码如下(示例):
data = pd.read_csv(
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.Executors;
@Component
public class TaskService {
private TaskService taskService;
private DelayQueue<Task> delayQueue = new DelayQueue<Task>();
@PostConstruct
private void init() {
taskService = this;
// 单个线程的线程池 可手动创建
Executors.newSingleThreadExecutor().execute(new Runnable() {
@Override
public void run() {
while (true) {
try {
Task task = delayQueue.take();
task.run();
} catch (Exception e) {
e.printStackTrace();
}
}
}
});
}
public void addTask(Task task){
if(delayQueue.contains(task)){
return;
}
delayQueue.add(task);
}
public void removeTask(Task task){
delayQueue.remove(task);
}
3.处理 我们的业务需求 类
public class OrderUnpaidTask extends Task {
private final Log logger = LogFactory.getLog(OrderUnpaidTask.class);
private int orderId = -1;
// 订单id , 延迟时间
public OrderUnpaidTask(Integer orderId, long delayInMilliseconds){
super("OrderUnpaidTask-" + orderId, delayInMilliseconds);
this.orderId = orderId;
}
public OrderUnpaidTask(Integer orderId){
super("OrderUnpaidTask-" + orderId, 60 * 60 * 1000);
this.orderId = orderId;
}
@Override
public void run() {
logger.info("系统开始处理延时任务---订单超时未付款---" + this.orderId);
// 业务处理
logger.info("系统结束处理延时任务---订单超时未付款---" + this.orderId);
}
}
4.使用
@Autowired
private TaskService taskService;
public void addTask() {
// 订单支付超期任务
taskService.addTask(new OrderUnpaidTask(orderId));
}
5. 自启动加入队列
因为这些队列是保存在内存中的,当我们关闭程序或重启程序后队列中的值都会消失,所以我们要在程序启动时将数据添加入队列中
SpringBoot给我们提供了两个接口来帮助我们实现这种需求。这两个接口分别为CommandLineRunner和ApplicationRunner。他们的执行时机为容器启动完成的时候
@Component
public class TaskStartupRunner implements ApplicationRunner {
@Autowired
private LitemallOrderService orderService;
@Autowired
private TaskService taskService;
@Override
public void run(ApplicationArguments args) throws Exception {
// 查询出所有 未支付的订单
List<LitemallOrder> orderList = orderService.queryUnpaid(SystemConfig.getOrderUnpaid());
for(LitemallOrder order : orderList){
LocalDateTime add = order.getAddTime();
LocalDateTime now = LocalDateTime.now();
LocalDateTime expire = add.plusMinutes(SystemConfig.getOrderUnpaid());
if(expire.isBefore(now)) {
// 已经过期,则加入延迟队列 立即执行
taskService.addTask(new OrderUnpaidTask(order.getId(), 0));
}
else{
// 还没过期,则加入延迟队列
long delay = ChronoUnit.MILLIS.between(now, expire);
taskService.addTask(new OrderUnpaidTask(order.getId(), delay));
}
}
}
}