定时执行任务的核心是Delayed接口。该接口主要定义了任务的排序方式和任务延迟时间。
主要代码如下:
1、该类是延时队列DelayQueue的实现类,是一个泛型类,该类需要接收2个参数,延迟时间和任务的实例对象。每个任务都会创建一个DelayOrderTask对象
/**
* 消息队列
* @className DelayOrderTask
* @author zhangyan
* @date 2020年2月18日 下午9:22:09
*/
public class DelayOrderTask implements Delayed {
private final long time; // 任务延迟时间
private final T task; // 任务类
public DelayOrderTask(long timeout, T task) {
this.time = System.nanoTime() + timeout;
this.task = task;
}
@Override
public int compareTo(Delayed o) {
DelayOrderTask> other = (DelayOrderTask>) o;
long diff = time - other.time;
if (diff > 0) {
return 1;
} else if (diff < 0) {
return -1;
} else {
return 0;
}
}
@Override
public long getDelay(TimeUnit unit) {
return unit.convert(this.time - System.nanoTime(), TimeUnit.NANOSECONDS);
}
@Override
public int hashCode() {
return task.hashCode();
}
/**
* 获取任务对象
* @author zhangyan
* @date 2020年2月20日 上午11:55:29
* @return
*/
public T getTask() {
return task;
}
}
2、该类维护的是一个DelayQueue集合,数据类型为DelayOrderTask,每一个任务所创建的DelayOrderTask对象都会和该任务执行的延迟时间一起传入DelayQueue集合中,集合会按照添加任务是传入的时间对任务进行排序(排序规则是通过DelayOrderTask. compareTo()进行定义的),到达任务执行时间之后(任务的具体延迟时间是通过DelayOrderTask. getDelay()定义的)调用线程去执行该任务
public class DelayOrderQueueManager {
private final static int DEFAULT_THREAD_NUM = 5;
private static int thread_num = DEFAULT_THREAD_NUM;
// 固定大小线程池
private ExecutorService executor;
// 守护线程
private Thread daemonThread;
// 延时队列
private DelayQueue> delayQueue;
/**
* 单例模式,返回队列管理实例
*/
private static DelayOrderQueueManager instance = new DelayOrderQueueManager();
private DelayOrderQueueManager() {
executor = Executors.newFixedThreadPool(thread_num);
delayQueue = new DelayQueue<>();
init();
}
public static DelayOrderQueueManager getInstance() {
return instance;
}
/**
* 队列管理类初始化
*/
public void init() {
/*daemonThread = new Thread(() -> {
execute();
});*/
// 上下2种写法是等价的,上面的Lambda写法只在java8及以后的版本中有效
daemonThread = new Thread(new Runnable() {
@Override
public void run() {
execute();
}
});
daemonThread.setName("DelayQueueMonitor");
daemonThread.start();
}
private void execute() {
while (true) {
// Map map = Thread.getAllStackTraces();
// System.out.println("当前存活线程数量:" + map.size());
// int taskNum = delayQueue.size();
// System.out.println("当前延时任务数量:" + taskNum);
try {
// 从延时队列中获取任务
DelayOrderTask> delayOrderTask = delayQueue.take();
if (delayOrderTask != null) {
Runnable task = delayOrderTask.getTask();
if (null == task) {
continue;
}
// 提交到线程池执行task
executor.execute(task);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* 添加任务
* @author zhangyan
* @date 2020年2月20日 下午8:43:28
* @param task 任务实例化对象
* @param time 任务延后时间
* @param unit 时间单位
* @param rf 任务类型
*/
public void put(Runnable task, long time, TimeUnit unit, String rf) {
// 获取延时时间
long timeout = TimeUnit.NANOSECONDS.convert(time, unit);
if (!"DelayOrderWorker".equals(rf)) {
DelayOrderTask> delayOrder = new DelayOrderTask<>(timeout, task);
delayQueue.put(delayOrder);
return;
}
Boolean ddzt = false; // 订单是否处于未支付状态
if ("0".equals(((DelayOrderWorker)task).getOrderSale_Detail().get("Flag").toString())) { // 订单属于未支付
ddzt = true;
}
Boolean flag = false; // 订单是否已在队列中
for (DelayOrderTask> delayOrderTask : delayQueue) {
DelayOrderWorker delayOrderWorker = (DelayOrderWorker) delayOrderTask.getTask();
delayOrderWorker.getOrderSale_Detail().get("AutoId");
// 如果该ID的订单已在队列中
if (((DelayOrderWorker)task).getOrderSale_Detail().get("AutoId").toString().equals(delayOrderWorker.getOrderSale_Detail().get("AutoId").toString())) {
flag = true; // 在队列中发现了该订单
if (ddzt) { // 订单在队列中切订单未支付
return;
} else { // 队列中的订单已支付进入下一个状态
DelayOrderTask> delayOrder = new DelayOrderTask<>(timeout, task);
delayQueue.remove(delayOrder);
}
}
}
if (!flag) { // 循环队列之后依然未发现该订单
if (ddzt) { // 订单属于未支付状态
DelayOrderTask> delayOrder = new DelayOrderTask<>(timeout, task);
delayQueue.put(delayOrder);
}
}
}
/**
* 删除任务
* @param task
* @return
*/
public boolean removeTask(DelayOrderTask> task) {
return delayQueue.remove(task);
}
}
3、该类是实现了接口Runnable的具体业务处理类
/**
* 具体的业务逻辑处理
* @className DelayOrderWorker
* @author zhangyan
* @date 2020年2月18日 下午9:20:59
*/
public class DelayOrderWorker implements Runnable {
/**
* 需要用的的实例,
* 本例用的是spring框架,在新线程中,@Autowired注解不再适用,spring不会为新线程自动注入实例对象,需要用其它方法处理,具体实现在下面
*/
private NamedParameterJdbcTemplate npJdbcTemplate;
/**
* 构造函数
*/
public DelayOrderWorker(Map OrderSale_Detail) {
// 在这里为属性npJdbcTemplate赋值,类ApplicationContextProvider是我们要实现的工具类
this.npJdbcTemplate = ApplicationContextProvider.getBean(NamedParameterJdbcTemplate.class);
this.OrderSale_Detail = OrderSale_Detail;
}
/**
* 定时任务的执行逻辑
* 将定时任务的逻辑写在这里,到指定的时间之后会自动执行该段代码
*/
@Override
public void run() {
int f = this.gbdd(OrderSale_Detail);
// Date d = new Date();
// SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS");
System.out.printf("关闭订单-%s,关闭状态-%d,订单产生时间-%s",OrderSale_Detail.get("AutoId"),f,OrderSale_Detail.get("UpdateTime"));
System.out.println();
}
}
4、用于队列的初始化和定时刷新队列。该类主要是动态向队列中添加数据。同时将该类本身的实例化对象也加入到队列中,形成一个定时刷新队列的无限循环
/**
* 用于队列的初始化以及队列的更新
* @className DelayOrderInit
* @author zhangyan
* @date 2020年2月19日 下午7:31:15
*/
public class DelayOrderInit implements Runnable {
private NamedParameterJdbcTemplate npJdbcTemplate;
public DelayOrderInit() {
this.npJdbcTemplate = ApplicationContextProvider.getBean(NamedParameterJdbcTemplate.class);
}
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
@Override
public void run() {
System.out.println("==================刷新队列===================");
Map paramMap = new HashMap<>();
List
5、该类很简单,同时也很重要,它就是一个无限循环队列的启动器,执行该方法后,队列启动,并进行自循环
public class StartRun {
public void test(){
System.out.println("开始执行 startRun 方法!!!============================");
DelayOrderQueueManager manager = DelayOrderQueueManager.getInstance(); // 获取队列管理实例
DelayOrderInit init = new DelayOrderInit();
manager.put(init, 100, TimeUnit.MILLISECONDS,"DelayOrderInit");
}
}
有时候我们需要队列在服务器启动时就开始执行,那么就需要在服务器启动后执行一下该方法就好,在Tomcat中可以通过配置文件来实现(在xml中配置该句,Tomcat就会在启动之后执行该配置指定的方法):
6、上面说了,在程序中动态开的线程不受spring管理,spring亦无法为类中的属性进行自动注入,需要我们手动实例化,但是如果只是用new的方式,在用到譬如数据库连接的时候就会很麻烦,一不小心就会出错。
我们使用ApplicationContextAware接口来实现属性的注入,这是spring提供的一个接口,通过集成该接口,我们可以拿到spring管理的所有bean,也就是说,所有可以用@Autowired注解自动注入的对象,我们都可以通过这个接口的实现类来拿到。
完整代码如下:
/**
* 手动获取上下文对象类
* 在某些特殊情况(如新线程中)下,自动注解的方式无法获取实例对象,可用此类获取
*
* @className ApplicationContextProvider
* @author zhangyan
* @date 2020年2月19日 下午2:29:18
*/
@Component
public class ApplicationContextProvider implements ApplicationContextAware {
/**
* 上下文对象实例
*/
private static ApplicationContext applicationContext;
/**
* 改方法会在服务器启动时自动执行一次,获取上下文对象
* @author zhangyan
* @date 2020年2月19日 下午2:31:21
* @param applicationContext
* @throws BeansException
* @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.ApplicationContext)
*/
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
ApplicationContextProvider.applicationContext = applicationContext;
}
/**
* 获取applicationContext
*
* @return
*/
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
/**
* 通过name获取Bean
* @description
* @author zhangyan
* @date 2020年2月19日 下午2:32:33
* @param name
* @return
*/
public static Object getBean(String name) {
return getApplicationContext().getBean(name);
}
/**
* 通过class获取Bean
* @author zhangyan
* @date 2020年2月19日 下午2:32:53
* @param clazz
* @return
*/
public static T getBean(Class clazz) {
return getApplicationContext().getBean(clazz);
}
/**
* 通过name,以及Clazz返回指定的Bean
* @author zhangyan
* @date 2020年2月19日 下午2:33:32
* @param name
* @param clazz
* @return
*/
public static T getBean(String name, Class clazz) {
return getApplicationContext().getBean(name, clazz);
}
}
当然,实现该功能还需要一个xml配置:
至此,一个完整的用Java语言,基于spring框架和Tomcat容器的定时执行功能到此结束。