在建设我自己的blog的过程中我意识到我需要有一个任务管理器来定时的执行某些任务,例如访问统计,我将每一个请求记录都记录下来用于后续的一些分析(被DDOS就玩咯,并不是一个好的解决方案),大概每隔5分钟会执行一次。虽然我的任务一般不会有什么太大的变化,但是我想后续在后台的界面上能够看到任务执行的历史,现在的任务列表以及立即执行一个任务的需求。虽然现成的框架是有的,例如Quartz,xxl-job等,但是我还是想自己简单造一个轮子用一下。

在进行这个功能实现的时候我大概思考了以下的问题:

  1. 我的任务多久执行一次,怎么定时时间间隔?
  2. 我如何去管理任务?要能进行延时、撤销以及立即执行。
  3. 我如何把控线程资源?

Q1: 我的任务多久执行一次,怎么定时时间间隔?

A: 这个问题实质上是想知道下一次的执行时间点是什么,以及如何更好的去完成这个执行时间的配置。我采用了spring的Cron表达式作为基础,使用了Spring中的CronExpression类来完成计算。通过传入一个指定的表达式表示我预期需要的时间点信息,然后再传入一个当前的时间,调用CronExpressionnext方法获取到下一次的执行时间点,居然的做法如下:

    public static LocalDateTime getNextTime(String expression) {
        CronExpression cronExpression = CronExpression.parse(expression);
    // 计算当前时间的下一次
    LocalDateTime now = LocalDateTime.now();
    return cronExpression.next(now);
}

Q2: 我如何去管理任务?要能进行延时、撤销以及立即执行。

A: 我一开始想直接用java的调度线程池ScheduledThreadPoolExecutor来进行任务调度的,但是后面一想不太对啊,这个会受到线程池本身的限制,任务无法及时的取消,只能等到这个任务执行的时候才会取消,如果我的任务的时间或者其他信息发生变化以后任务是无法撤回的,这基本就一棒子打死这个想法;。所以最后采用的方案是用最简单的一个ConcurrentHashMap来维护,任务的Id作为Key,Value是这个任务对应的线程,这样就能直接掌控到每一个任务的生死。是的,我为每一个任务创建了一个线程,这样就不会有任务的无法因为获取不到线程资源而延后调度。

撤销:直接根据任务的id从map中获取到任务,然后关闭线程;

延时:关于延时的问题,就再Q1的基础上,得到下一次运行的时间,然后计算出到下一个时间点的时间差,任务运行之前之间使用Thread.sleep睡到指定时间;

立即执行: 如果我的任务要立即执行怎么办?简单的,就直接开一个线程执行就好了,但是这个线程不放到对应的map中做定时调度的任务管控,而是放到一个临时的map中做维护,这样线程也不至于没地方管;

Q3: 我如何把控线程资源?

A: 就是不把控,一个任务一个线程,任务并不会很多,而且也好进行延迟执行的控制,在使用Sleep的情况下,仅消耗内存资源不会消耗CPU资源