JAVA中延时队列定时执行任务的实现

定时执行任务的核心是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> data = npJdbcTemplate.queryForList(sql, paramMap);
	     DelayOrderQueueManager manager = DelayOrderQueueManager.getInstance();  	// 获取队列管理实例
	     
	     for (Map map : data) {
        	try {
				long updateTime=format.parse(map.get("UpdateTime").toString()).getTime();
				long date2=format.parse(map.get("data").toString()).getTime();
				long effectivetime = Long.parseLong(map.get("effectivetime").toString());
				long date = updateTime + effectivetime - date2;
				date = date > 0 ? date : 10;
				
				DelayOrderWorker work = new DelayOrderWorker(map);// 创建队列任务
				manager.put(work, date, TimeUnit.MILLISECONDS,"DelayOrderWorker");  	// 添加消息至定时队列
			} catch (ParseException e) {
				e.printStackTrace();
			}
		}
	     
	     // 将刷新队列的功能重新加入队列,以保证定时刷新队列
	     DelayOrderInit init = new DelayOrderInit();
	     manager.put(init, 60000, TimeUnit.MILLISECONDS,"DelayOrderInit");
	}
}

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容器的定时执行功能到此结束。

 

 

 

 

 

 

 

 

 

你可能感兴趣的:(SpringMVC,Java基础)