Java中定时任务Timer引发的生产问题

Java中定时任务Timer引发的生产问题

该生产问题是其他项目组中存在的,但因为当时我有参与这模块的代码评审,当时也指出来该地方有问题,建议修改为ScheduledExecutorService或多测试。但是后面重视程度不够,导致引发了生产问题。

问题代码分析

其中有如下部分代码。

Timer timer = new Timer();
timer.schedule(new TimerTask() {
        @Override
        public void run() {
            try {
                //TODO
            } catch (InterruptedException e) {
                e.printStackTrace(); 
            }
        }
    }, 10*1000, 5000); 

通过这种单线程的方式实现,在存在多个定时任务的时候便会存在问题:若任务A执行时间过长,将导致任务B延迟了启动时间!

另外一个问题,应该是属于设计的问题:若任务线程在执行队列中某个任务时,该任务抛出异常,将导致线程因跳出循环体而终止,即Timer停止了工作!

所以,这个地方一旦抛出异常,则导致线程终止,我们线上就是因为某个数据执行时抛出了异常,导致整批数据终止,这是我们不愿意看到的。该schedule的API注释是有说明的。建议用ScheduledThreadPoolExecutor替换,但是使用它也有很多注意事项。

ScheduledThreadPoolExecutor

ScheduledThreadPoolExecutor继承自ThreadPoolExecutor,所以本质上说ScheduledThreadPoolExecutor还是一个线程池。它也有coorPoolSize和workQueue,接受Runnable的子类作为任务。

特殊的地方在于它实现了自己的工作队列DelayedWorkQueue,该任务队列的作用是按照一定顺序对队列中的任务进行排序。比如,按照距离下次执行时间的长短的升序方式排列,让需要尽快执行的任务排在队首,“不那么着急”的任务排在队列后方,从而方便线程获取到“应该”被执行的任务。除此之外,ScheduledThreadPoolExecutor还在任务执行结束后,计算出下次执行的时间,重新放到工作队列中,等待下次调用。

ScheduledThreadPoolExecutor可以说是Timer的多线程实现版本,连JDK官方都推荐使用ScheduledThreadPoolExecutor替代Timer。下面是替代的方式。

ScheduledExecutorService pool = Executors.newSingleThreadScheduledExecutor();  
//使用
pool.scheduleAtFixedRate(new TimerTask() {  
        @Override  
        public void run() {  
            //TODO
        }  
    }, 0 ,5, TimeUnit.SECONDS);    

另外,当我们用springboot时,也可以用这种方式。

使用 @Schedule注解的方式

核心类:@EnableScheduling @Scheduled

 @Scheduled(fixedDelay = 5000)
 public void doSomething(){
     //TODO
 }

使用 SchedulingConfigurer接口 + runable线程实现

核心类:Executor,ScheduledThreadPoolExecutor,SchedulingConfigurer,Runnable

public class Task extends CommonTask implements Runnable {
    //1.业务代码
    public void run() {}
}
//2.任务执行
public void configureTasks(final ScheduledTaskRegistrar taskRegistrar) {
    taskRegistrar.setScheduler(executor);
    platform.getTasks().forEach(final task -> taskRegistrar.addTriggerTask(task, triggerContext -> {
        ConTrigger trigger = new CronTrigger(task.getCron());
        Date nextExecDate = trigger.nextExecutionTime(triggerContext);
        return nextExecDate;
    }));
}

使用quartz框架Api实现

  1. 使用springboot整合quartz框架方式

核心类:Job,AdaptableJobFactory,SchedulerFactoryBean,Scheduler,TriggerKey。

在这里插入图片描述
在这里插入图片描述
三种方式的使用比较。

功能@Scheduled注解SchedulingConfigure接口+runable线程quartz
并行运行需要新增配置文件需要配置注解默认并发
动态新增复杂,暂无好方案复杂,暂无好方案使用自带API
动态修改使用${} cron表达式使用组合模式,修改线程使用自带API
可监控信息复杂,暂无好方案可以使用api获取简单的信息,不完善使用自带API,但是能获取信息也相对较少

结合几种实现方式和实际需求的覆盖率,使用quartz+多线程的方式应该是最合适的方案,不过在监控信息方面,所有的方案均不能满足实际需求,所以,需要额外的引入,日志,并且在业务端手动的新增监控信息,才能达到详细的记录、监控的目的。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值