过去这段时间我学到的最有意思的编程技巧是通过阻塞队列隔离高低速的数据处理请求。
最近这周我打算尝试实现能够响应cron表达式的定时线程。一般来说这样做有三个思路:
1、使用quartz工具
2、使用java自带的timer组件实现
3、将定时线程动态整合到springboot的schedule框架中
第一种方法比较经典,但是会引入第三方库,如果用这种策略那么系统将会有一个和springboot共存的定时任务执行系统,这是较为危险的事情。
第二种方法比较基础,但是在timer上直接做开发,需要做的工作会比较多。
第三种方法是最好的,既能够兼容springboot,又不会引入额外的组件。但是缺点在于springboot的schedule使用上一版是用注解的方式而不是直接引出函数进行调用,官方没有这样做的示例。
那么这件事做下来就需要从源码着手了,但是这类定时系统本身我还不太熟,对源码的关键部分捋了一下,大概的认识是:
1、定时器是一个多线程的执行方式,会把cron语句翻译成在当前时刻开始多长时间执行一次的格式。
2、定时器实际执行的时候,都是按照多线程的方式执行的,也就是说所有的定时函数都会被翻译为一个runnable的多线程程序。
3、定时器按照时间响应使用的是Trigger工具,这个工具的作用是计算上次完成后下次什么时候才可以执行,然后生成一个需要等待这么长时间才能执行的任务,放到blockqueue队列里,让一个死循环的线程阻塞到这个队列上检查所有队列里的任务,一旦有需要执行的,则从队列出队,使用java的future模式执行。
这回答了一个问题,就是不使用死循环怎样才能检查出每个时刻的任务是否有需要执行的,答案是没有,还是需要死循环。
但是这意味着我们可以通过设定trigger构造可以执行的东西,在查资料和尝试后,发现ThreadPoolTaskScheduler这个对象是可以用Autowired的方式取出来写入任务的,这样实现如下:
threadPoolTaskScheduler.schedule(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName()); } }, new Trigger() { @Override public Date nextExecutionTime(TriggerContext triggerContext) { CronTrigger trigger = new CronTrigger("0/1 * * * * ?"); return trigger.nextExecutionTime(triggerContext); } });
但是测试后发现ThreadPoolTaskScheduler这个对象的线程数量默认只有一个(不至于对系统带来过大计算负担),而且任务一旦开始,如果有新增没问题,但是要彻底删掉一个启动的任务似乎就会很麻烦,看样子似乎需要完整关闭整个TaskScheduler所有任务重新加载才可以。具体的方式有待下期探索。