DelayQueue实现指定时间后业务取消或确认

DelayQueue实现指定时间后业务取消或确认

需求背景:

项目中经常会遇到一些业务在某些情况下需要延迟一些时间后进行取消或确认操作,常见的就是订单支付时15分钟内未支付取消订单。具体使用如下:

1.先定义一个执行延迟任务的接口,执行延迟任务的业务相关类必须实现此接口(也就是我们xxxService),具体如下:

你也可以补充自定定义一个接口,提供你需要的功能

import java.util.Map;
/**
* 使用DelayIte时必须实现此接口, 用于延迟任务执行
* @Author sxt
* @Date 2023/8/17 15:58
**/
public interface DelayService {
/**
* 执行延迟任务业务处理,无返回值
* @param params service执行的参数数据
*/
void executeService(Map<String, Object> params);
}

2.创建一个通用接受延迟任务的class,并实现Delayed接口。主要目的是为了接受任何一个业务需要的延迟任务数据并进行到点执行。接受一个泛型, S是对应的Service,此泛型必须实现上一步定义的DelayService接口,主要是到点执行对应的业务逻辑。

具体实现如下:

import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;
/**
* 简单-延迟实例
* @Author sxt
* @Date 2023/8/17 14:34
**/
@Slf4j
public class DelayItem<S extends DelayService> implements Delayed {
/** 标识 */
private String bizId;
/** 延迟时间 单位毫秒*/
private long expTime;
/** 执行的dao对象 */
private final S service;
/** service执行的参数 */
private Map<String, Object> params;
/**
* 构建延迟数据实例
* @param service 执行延迟任务的service
* @param params service需要的参数数据
* @param bizId 延迟任务标识
* @param createTime 延迟任务创建时间 毫秒
* @param expTime 延迟时间(多长时间后执行) 秒
*/
public DelayItem(S service, Map<String, Object> params,String bizId, long createTime, long expTime){
this.service = service;
this.bizId = bizId;
this.params = params;
//过期时间= 创建时间+延迟时间
this.expTime = TimeUnit.SECONDS.toMillis(expTime) + createTime;
}
private DelayItem(){
service = null;
}
/**
* 获取延迟时间
* @param unit
* @return
*/
@Override
public long getDelay(TimeUnit unit) {
/*log.info("到期了吗");*/
long l = expTime - System.currentTimeMillis();
return unit.convert(l, TimeUnit.MILLISECONDS);
}
/**
* 延迟队列用来比较执行顺序的, 延迟时间比较
* @param o
* @return
*/
@Override
public int compareTo(Delayed o) {
return Long.compare(expTime, ((DelayItem) o).expTime);
}
public long getExpTime() {
return expTime;
}
public S getService() {
return service;
}
public Map<String, Object> getParams() {
return params;
}
public String getBizId() {
return bizId;
}
public void executeTask() {
try {
Map<String, Object> paramsMap = this.params;
if (null == paramsMap) {
paramsMap = new HashMap<>();
}
DelayItem delayItem = new DelayItem();
delayItem.params = params;
delayItem.expTime = expTime;
delayItem.bizId = bizId;
paramsMap.put("params", JSONUtil.toJsonStr(delayItem));
service.executeService(paramsMap);
}catch (Exception e) {
log.error("延迟任务执行异常: {}", e.getMessage());
}
}
}

3.定义一个延迟任务工具类,方便添加延迟任务

具体如下:

import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.test.DelayItem;
import com.test.DelayTaskService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.*;
/**
* 描述:延迟任务工具类,在使用前需要调用DelayUtil.start()开启延迟任务监控线程
*
* @author SXT
* @version 1.0
* @date 2023/8/17
*/
public class DelayUtil {
private static Logger log = LoggerFactory.getLogger(DelayUtil.class);
/** 是否开启线程执行延迟任务, true:已开启 */
private static boolean isRun = false;
private static ExecutorService threadService = null;
private static DelayQueue<DelayItem> queue = new DelayQueue<>();
/**
* 向延迟队列中添加执行的任务
* @param item
*/
public static void addItem (DelayItem item) {
queue.put(item);
}
/**
* 移除延迟队列中需要执行的任务
* @param bizId 移除指定标识的元素
*/
public static void remove (String bizId) {
if (ToolUtils.isNotEmpty(bizId)) {
if (!queue.removeIf(el -> Objects.nonNull(el.getBizId()) && Objects.equals(bizId, el.getBizId()))) {
log.error("延迟任务bizId:{}移除失败!", bizId);
return;
}
log.info("延迟任务bizId: {} 移除成功", bizId);
}
}
private static ExecutorService getThreadService(){
return new ThreadPoolExecutor(
// 核心线程池大小
3,
// 最大线程池大小
6,
// 线程最大空闲时间
15,
// 时间单位
TimeUnit.SECONDS,
// 线程等待队列
new LinkedBlockingQueue<>(),
// 线程创建工厂
new ThreadFactoryBuilder().setNameFormat("Delay-pool-%d").build(),
// 拒绝策略
new ThreadPoolExecutor.CallerRunsPolicy()
);
}
/**
* 阻塞获取队列元素
*/
public static DelayItem take () {
try {
return queue.take();
} catch (InterruptedException e) {
log.error("阻塞获取延迟实例失败: ", e);
}
return null;
}
/**
* 非阻塞获取队列元素, 没有到期的元素直接返回 null
*/
public static DelayItem poll () {
return queue.poll();
}
/**
* 开启线程执行延迟队列任务
*/
public static synchronized void run () {
if (isRun) {
return;
}
isRun = true;
if (null == threadService) {
threadService = getThreadService();
}
log.info("开始运行延迟队列~~~");
threadService.execute(() -> {
while (true) {
//获取要执行的任务
DelayItem task = SimpleDelayUtil.take();
if (null == task) {
log.warn("延迟任务未获取到数据....");
continue;
}
task.executeTask();
log.info("延迟任务执行, 执行时间: {}", LocalDateTime.now());
}
});
}
}

以上就是使用jdk提供的DelayQueue实现的通用延迟队列实现。

下面是具体的使用示例:

1.分别创建UserService和PersonService并是实现自定义的DelayService接口

public class UserService implements DelayTaskService<User> {
private static Logger log = LoggerFactory.getLogger(UserService.class);
@Override
public void executeTask(Map<String,Object> data) {
log.info("UserService服务执行...");
log.info("data: {}", Arrays.toString(data.toArray()));
}
}
/*****************************************************/
public class PersonService implements DelayTaskService<Person> {
private static Logger log = LoggerFactory.getLogger(PersonService.class);
@Override
public void executeTask(Map<String, Object> data) {
log.info("PersonService服务执行...");
log.info("data: {}",data);
}
}

2.进行测试

3.测试结果:

测试延迟任务执行:

测试取消延迟任务:

总结:

  1. 此方式只适用于数据量不大的情况下使用,适用于单机版
  2. 在项目中使用需要注意在项目启动时或启动后执行DelayUtil.start()开启延迟线程
  3. 服务停止会将未执行的延迟任务清空,所有在启动项目时要做获取需要执行的延迟任务数据,并添加到延迟队列中

在使用Spring框架下,可以定义一个class专门在项目启动时执行总结的点三点。具体可以时在Bean实例化后进行(实现InitializingBean接口),也可以使用@PostConstruct。当然也可以使用其他的方式,如使用CommandLineRunner在服务启动后自动执行

posted @   酸菜鱼没有鱼  阅读(45)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· winform 绘制太阳,地球,月球 运作规律
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 上周热点回顾(3.3-3.9)
点击右上角即可分享
微信分享提示