目的
当订单超过指定时间(毫秒),未支付时,系统自动取消订单。
方案
- 初步方案
1、定时从数据库获取待取消订单, 放入集合中。
2、将剩余10分钟内待取消订单创建线程,加入(缓存)线程池中,休眠(取消时间-当前时间)后执行取消任务。
3、任务执行完成后移除任务。缺点:未限制线程数量。如果某个时间段退款过多,会导致创建过多线程。
- 优化方案1
1、将上述方案缓存线程池,修改为定长线程池。
缺点:运行线程占满时,会导致后续新增的取消订单(退款时间小于休眠中的订单),无法按照指定时间执行。
原因:例如线程池中,可同时执行的线程数为2。当前线程池中,已有两个待取消订单在执行队列中(休眠了执行时间后执行)。导致新增的取消订单任务,需等待前两个中的某一个,执行完成后,才能执行新增的取消订单任务。
- 最终方案 (如有其他方案、此方案有待改进的地方,欢迎评论一起探讨)
1、定时从数据库获取待取消订单, 放入集合中。将集合按照取消时间,从小到大排序。并通知步骤2。
2、获取集合中,第一个任务的订单取消时间,wait(订单取消时间-当前时间)后将线程将线程加入线程池中,执行取消订单任务。无任务时wait()。
3、任务执行完成后移除任务。
实现代码 (待删除无用代码)
package com.gtmc.uccs.common.util;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import lombok.extern.slf4j.Slf4j;
/**
* 定时处理工具类
*
* @description: 定时处理工具类
* @author: chenfei
* @create: 2020-12-23 16:21:55
*/
@Slf4j
public abstract class TimingProcess {
/**
* 执行任务的线程池
*/
private final ExecutorService taskExecuteThreadPool;
/**
* 待执行的任务队列
*/
private final List> taskQueue = Collections.synchronizedList(new LinkedList>());
/**
* 执行中的任务队列
*/
private final List> readiedTaskQueue = Collections.synchronizedList(new LinkedList>());
/**
* 调度程序占用的线程数
*/
private final int processTread = 2;
/**
* 循环调用间隔
*/
private int sleepTime;
private static final SimpleDateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
/**
* 创建定时处理程序 立即执行当前可执行方法 并在指定时间循环调用
*
* @param sleepTime 循环调用间隔时间 单位:分
* @author: chenfei
* @create: 2020-12-24 13:19:01
*/
public TimingProcess(int sleepTime) {
this(sleepTime, 5);
}
/**
* 创建定时处理程序 立即执行当前可执行方法 并在指定时间循环调用
*
* @param sleepTime 循环调用间隔时间 单位:分
* @author: chenfei
* @create: 2020-12-24 13:19:01
*/
public TimingProcess(int sleepTime, int maxTaskExecuteNum) {
this.sleepTime = sleepTime;
taskExecuteThreadPool = Executors.newFixedThreadPool(maxTaskExecuteNum+processTread);
this.executeTask();
createThreadRunDispatchTask(sleepTime);
}
/**
* 创建一个线程 根据循环调用调度任务
*
* @description: 根据指定时间循环调用调度任务
* @param sleepTime 间隔多长时间循环调用
* @author: chenfei
* @create: 2020-12-24 12:50:22
*/
private void createThreadRunDispatchTask(int sleepTime) {
taskExecuteThreadPool.execute((new Runnable() {
@Override
public void run() {
while(true) {
dispatchTaskNow();
try {
Thread.sleep(sleepTime*60*1000);
} catch (InterruptedException e) {
log.error("休眠被异常打断", e);
}
}
}
}));
}
/**
* 执行调用任务 循环扫描队列 将可执行任务创建线程进行定时处理
*
* @description: 执行调用任务 循环扫描队列 将可执行任务创建线程进行定时处理
* @author: chenfei
* @create: 2020-12-24 13:01:44
*/
private synchronized void dispatchTaskNow() {
this.sortTaskQueue();
log.debug("任务调度 开始执行 ... 当前队列等待执行任务: {}个 预备中的任务: {}", this.taskQueue.size(), readiedTaskQueue.size());
Iterator> iterator = this.taskQueue.iterator();
while(iterator.hasNext()) {
Task task = iterator.next();
Date taskRunTime = task.getTaskRunTime();
// 如果执行日期小于定时循环时间 创建任务并加入到执行中的队列
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.MINUTE, sleepTime);
if(calendar.getTime().compareTo(taskRunTime) >= 0) {
readiedTaskQueue.add(task);
synchronized (readiedTaskQueue) {
readiedTaskQueue.notify();
}
}
}
this.taskQueue.removeAll(readiedTaskQueue);
log.debug("任务调度 执行结束 ... 当前队列等待执行任务: {}个 预备中的任务: {}", this.taskQueue.size(), readiedTaskQueue.size());
}
/**
* 执行任务
*
1)当前预备队列中有可执行任务时,取时间最小的作为等待时间,等待时间结束时开始处理任务。
*
2) 当前预备队列无可执行任务时,一直等待直至被唤醒
*
*
* @description: 执行任务
* @author: chenfei
* @create: 2020-12-24 23:38:57
*/
private void executeTask() {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
while(true) {
synchronized (readiedTaskQueue) {
// 排序预备中的任务
Collections.sort(readiedTaskQueue);
try {
if(readiedTaskQueue.size()<=0) {
readiedTaskQueue.wait();
}
Task task = readiedTaskQueue.get(0);
Date taskRunTime = task.getTaskRunTime();
// 第一次判断需要睡眠多久
long sleepTime = taskRunTime.getTime()-System.currentTimeMillis();
if(sleepTime>0) {
readiedTaskQueue.wait(sleepTime);
}
// 第二次获取时间 判断当前是否可执行人 有可能是被调度器唤醒 非等待时间结束
sleepTime = taskRunTime.getTime()-System.currentTimeMillis();
if(sleepTime<=0) {
taskExecuteThreadPool.execute(taskToThread(task));
readiedTaskQueue.remove(task);
}
} catch (InterruptedException e) {
log.error("线程等待被异常唤醒", e);
}
}
}
}
});
taskExecuteThreadPool.execute(thread);
}
/**
* 创建一个线程执行任务内容
*
* @description: 创建一个线程执行任务内容
* @param t
* @param date 定时处理的时间
* @return
* @author: chenfei
* @create: 2020-12-24 13:02:45
*/
private Runnable taskToThread(final Task task) {
return new Runnable() {
@Override
public void run() {
try {
log.debug("任务定时执行时间: {} 当前系统时间: {}",SIMPLE_DATE_FORMAT.format(task.getTaskRunTime()), SIMPLE_DATE_FORMAT.format(new Date()));
handle(task);
}finally {
log.debug("{} 任务执行结束 即将从队列中移除", task);
readiedTaskQueue.remove(task);
}
}
};
}
/**
* 任务执行的内容
*
* @description: 需执行的任务
*
* @param task 任务
* @author: chenfei
* @create: 2020-12-23 17:34:19
*/
public abstract void handle(Task task);
/**
* 添加一个任务到队列 当前有可执行任务时,会立即进入执行队列。 其余进去待调度队列
* @description: 添加一个任务到队列
* @param task 任务
* @author: chenfei
* @create: 2020-12-24 21:26:36
*/
public void addTask(Task task) {
if(task == null) {
return;
}
synchronized (this) {
if(readiedTaskQueue.contains(task) || taskQueue.contains(task)) {
return;
}
taskQueue.add(task);
}
dispatchTaskNow();
}
/**
* 添加全部任务
*
* @description: 添加全部任务
* @param tasks 任务集合
* @author: chenfei
* @create: 2020-12-24 15:36:02
*/
public void addAllTask(List> tasks) {
if(tasks == null) {
return;
}
synchronized (this) {
for (Task task : tasks) {
if(readiedTaskQueue.contains(task) || taskQueue.contains(task)) {
return;
}
taskQueue.add(task);
}
this.sortTaskQueue();
}
dispatchTaskNow();
}
private void sortTaskQueue() {
Collections.sort(this.taskQueue);
}
/**
* 任务类
*
* @program: common-core
* @description: 任务类
* @author: chenfei
* @create: 2020-12-24 21:53:58
*/
public static class Task implements Comparable>{
/**
* 执行任务所需的参数对象
*/
private T taskParam;
/**
* 任务运行时间
*/
private Date taskRunTime;
public Task(T taskParam, Date taskRunTime) {
super();
this.taskParam = taskParam;
this.taskRunTime = taskRunTime;
}
public T getTaskParam() {
return taskParam;
}
public void setTaskParam(T taskParam) {
this.taskParam = taskParam;
}
public Date getTaskRunTime() {
return taskRunTime;
}
@Override
public int compareTo(Task task) {
return this.getTaskRunTime().compareTo(task.getTaskRunTime());
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((taskParam == null) ? 0 : taskParam.hashCode());
result = prime * result + ((taskRunTime == null) ? 0 : taskRunTime.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
@SuppressWarnings("unchecked")
Task other = (Task) obj;
if (taskParam == null) {
if (other.taskParam != null)
return false;
} else if (!taskParam.equals(other.taskParam))
return false;
if (taskRunTime == null) {
if (other.taskRunTime != null)
return false;
} else if (!taskRunTime.equals(other.taskRunTime))
return false;
return true;
}
}
}