Spring前一次定时任务没执行完,下次任务要求再次执行

业务背景:正常开发情况下,spring的定时任务只有当前任务线程执行完毕后才会扫描下一次定时任务,即为默认的同步任务。

譬如定时表达式@Scheduled(cron = "0 0/1 * * * ?")

意味当前定时任务理想情况下需要1分钟跑一次,但是当遇到有时间要求的定时任务时,同时又有新的增量任务进来时,则需要启动异步定时任务。

如果采用的是spring自带的Scheduled注解实现的话

第一步在springboot启动类上添加@EnableAsync注解启动异步任务

第二步在实际的定时任务方法上添加@Async()注解

这样的话当当前任务正在执行时,即还未执行完毕时,当下次1分钟的时候也会再次扫描该定时任务。

示例代码:

@SpringBootApplication
@EnableAsync
@EnableFeignClients
@EnableScheduling
public class EpaiMarketApplication {
   public static void main(String[] args) {
      SpringApplication.run(EpaiMarketApplication.class, args);
   }
}
@Component
@Slf4j
public class AutoIncrNewsJob {

    @Resource
    private NewsService newsService;

    @Scheduled(cron = "0 0/1 * * * ?")
    private void incrNewsViewCount() {
        newsService.incrNewsViewCount();
    }
}
@Async()
public void incrNewsViewCount() {
        try{
            List needIncrViewCountNewsList=baseNewsInfoMapper.selectNeedIncrViewCountList();
            if(CollectionUtils.isEmpty(needIncrViewCountNewsList)){
                return;
            }
            //使用parallel并发进程执行
            needIncrViewCountNewsList.stream().parallel().forEach(news->{
                Boolean flag = redisTemplate.opsForValue().setIfAbsent("news_count_incr_switch_"+news.getNewsId(), "lock",60*30, TimeUnit.SECONDS);
                if(flag){
                     try {
                        Integer targetViewCount = news.getTargetViewCount();
                        Integer initMinute = news.getInitMinute();
                        Integer totalViewCount = news.getTotalViewCount();
                        Integer diffViewCount=targetViewCount-totalViewCount;
                        //取出redis值进行+1操作
                        Integer redisViewCount=0;
                        if(redisClient.get(NewsConstant.REDIS_COUNT_PREX+ news.getNewsId())!=null){
                            redisViewCount = Ints.tryParse(redisClient.get(NewsConstant.REDIS_COUNT_PREX + news.getNewsId()).toString());
                        }
                        int step=diffViewCount%(initMinute*60)==0?diffViewCount/(initMinute*60):diffViewCount/(initMinute*60)+1;
                        DecimalFormat dF = new DecimalFormat("0.00");
                        //(float) successCount / (successCount + failCount)) * 100
                        double times=Double.valueOf(dF.format((float)step*initMinute*60/diffViewCount==0?1:(float)step*initMinute*60/diffViewCount));
                        log.info("redis 新增步长:{},资讯id:{},间隔秒数:{}",step,news.getNewsId(),times);
                        for(int i=1;i<=initMinute*60;i++){
                            try {
                                if(redisClient.get(NewsConstant.REDIS_COUNT_PREX+ news.getNewsId())!=null){
                                    redisViewCount = Ints.tryParse(redisClient.get(NewsConstant.REDIS_COUNT_PREX + news.getNewsId()).toString());
                                }
                                if(redisViewCount>=targetViewCount){
                                    break;
                                }
                                Thread.sleep((long) (1000*times));
                                log.info("redis 新增步长:{},资讯id:{}",step,news.getNewsId());
                                redisClient.incr(NewsConstant.REDIS_COUNT_PREX + news.getNewsId(),step);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                        BaseNewsInfo baseNewsInfo=new BaseNewsInfo();
                        baseNewsInfo.setNewsId(news.getNewsId());
                        baseNewsInfo.setTotalViewCount(news.getTotalViewCount()+redisViewCount);
                        baseNewsInfoMapper.updateByPrimaryKeySelective(baseNewsInfo);
                    }catch(Exception e){
                         log.error("定时增加资讯初始浏览量异常,错误信息:{},资讯id:{}",e.toString(),news.getNewsId());
                     }finally {
                         //释放锁资源
                         redisTemplate.delete("news_count_incr_switch_"+news.getNewsId());
                     }
                }
            });
        }catch(Exception e){
                log.error("定时增加资讯初始浏览量异常,错误信息:{}",e.toString());
        }
}

通过两次注解即可实现增量的任务加入到扫描队列中,同时在定时任务执行过程中要注意索住执行对象。

你可能感兴趣的:(多线程,Java,spring,java,后端)