Spring定时任务&Springboot异步任务

1、cron表达式

语法:秒 分 时 日 月 周 年(spring不支持)

http://www.quartz-scheduler.org/documentation/quartz-2.3.0/tutorials/crontrigger.html

在这里插入图片描述
实例:
在这里插入图片描述
在线生成cron表达式:https://cron.qqe2.com/

2、Springboot整合定时任务和异步任务

定时任务
1、开启定时任务@EnableScheduling
2、@Scheduled 开启一个定时任务
3、自动配置类TaskSchedulingAutoConfiguration
4、定时任务会开启一条线程A去执行,就算有多个定时任务,也是使用这个线程A去一个一个执行。开启@EnableAsync @Async异步任务的话。就能够使用默认的线程池,来执行定时任务了

异步任务
1、@EnableAsync开启异步任务
2、@Async给要异步执行的方法标上注解
3、自动配置类TaskExecutionAutoConfiguration
属性绑定在TaskExecutionProperties
底层也是有用到线程池的,默认核心线程数是8,大概看了一下自动配置类,应该是如果系统中没有自定义的线程池,就创建一个新的线程池,如果已经有了,就直接用自定义的线程池

示例:

@Slf4j
@Component
@EnableAsync
@EnableScheduling
public class HelloSchedule {
    /**
     *1、在spring中cron6位组成,不允许第七位年
     *2、在周的位置,1-7代表周一到周日或MON-SUN
     *3、定时任务不应该阻塞。默认是阻塞的
     *      3.1、可以让业务以异步的方式运行,自己提交到线程池
     *      3.2、使用定时任务自带的线程池
     *          定时任务自带线程池的线程数:spring.task.scheduling.pool.size=5
     *          有些springboot版本设置了这个也不好使,可能是bug
     *      3.3、让定时任务异步执行,直接让整个定时任务方法异步执行,而不是方法里的业务异步执行
     *           springboot提供了异步任务的功能,除了我们自己写一个线程池,把我们自己要执行的
     *           业务丢给线程池执行之外,springboot还支持异步任务
     *           一、类上加注解@EnableAsync,开启异步任务
     *           二、给要异步执行的方法标上注解@Async
     *
     *
     *
     */
    @Async
    @Scheduled(cron = "* * * ? * 3")
    public void hello() throws InterruptedException {
        log.info("hello...");
        //模拟阻塞,业务执行时间很长
        Thread.sleep(3000);
    }
}

配置:可以参考TaskExecutionProperties来进行配置

##异步任务设置
spring.task.scheduling.pool.size=5
spring.task.execution.pool.max-size=50

3、分布式下定时任务的问题

三台机器A1,A2,A3,都有一个定时任务,同一段程序,定时任务的设置都一样,等时间一到,它们就都同时启动了定时任务,就要同时执行业务代码,就会出现幂等性问题
在这里插入图片描述
解决:
应该是定时任务,只能有一台机器在执行,不能所有机器同时执行。
使用分布式锁解决。
在这里插入图片描述

@Slf4j
@Service
public class seckillSkuScheduled {
    @Autowired
    private SeckillService seckillService;
    @Autowired
    private RedissonClient redissionClient;

    private final String upload_lock="seckill:upload:lock";

    //保证幂等性问题
    @Scheduled(cron = "*/5 * * * * ?")
    public void uploadSeckillSkuLatest3Days() {
        log.info("上架秒杀的商品");

        //分布式锁
        RLock lock = redissionClient.getLock(upload_lock);
        //锁的超时时间
        lock.lock(10, TimeUnit.SECONDS);
        try{
            seckillService.uploadSeckillSkuLatest3Days();
        }finally {
            //释放锁
            lock.unlock();
        }
    }
}

4、SchedulingConfigurer接口定义定时任务

springboot定时任务除了使用注解的方式之外,还能同时实现接口SchedulingConfigurer来定义定时任务。
例如

@Slf4j
public class UpdateUserScheduleTask extends AbstractUpdateUserExpireTimeTask implements UpdateUserExpireTimeTask , SchedulingConfigurer {
    //常用cron表达式
    //10秒执行一次:*/10 * * * * ?
    //每天凌晨两点执行一次:0 0 2 * * ?
    @Override
    public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
        scheduledTaskRegistrar.addCronTask(()->{
            execute();
        },"*/5 * * * * ?");
        log.info("定时任务注册器加载完成");
    }
}

配置类:

@EnableScheduling
@Bean
@ConditionalOnMissingBean
public UpdateUserExpireTimeTask updateUserExpireTimeTask(){
    return new UpdateUserScheduleTask();
}

自定义接口:

public interface UpdateUserExpireTimeTask {
    void execute();
}

抽象类:

@Slf4j
public class AbstractUpdateUserExpireTimeTask implements UpdateUserExpireTimeTask{
    @Autowired
    private UserService userService;

    @Override
    public void execute() {
        long startTime = System.currentTimeMillis();
        log.debug("开始执行任务---->更新用户是否过期-->startTime:" + startTime);
        LocalDateTime now = LocalDateTime.now().with(LocalTime.MIN);
        //开始生效的用户
        userService.updateIsExpired(ExpireTypeEnum.UNEXPIRED,ExpireTypeEnum.EXPIRED,now,null);
        //开始过期的用户
        userService.updateIsExpired(ExpireTypeEnum.EXPIRED,ExpireTypeEnum.UNEXPIRED,null,now);
        log.debug("任务执行结束,共耗时:" + (System.currentTimeMillis() - startTime) + "毫秒");
    }
}

按道理来说,我定时任务直接实现接口SchedulingConfigurer ,就行了,为啥还需要接口UpdateUserExpireTimeTask
1、其实UpdateUserExpireTimeTask 可以用来做一个标记,因为如果有两个定时任务都实现了接口SchedulingConfigurer ,在配置类中注入的时候就会有冲突因此,通过自定义一个接口来区别
2、控制该定时任务是可以执行,还是不让执行,也就是控制它的开关,为什么说能控制它的开关呢?因为如果我不想使用springboot的定时任务,我想使用第三方的定时任务框架,去做定时任务,那么我只需要将定时任务去实现接口UpdateUserExpireTimeTask以及第三方定时任务提供的接口就行,然后我在配置类中注入该定时任务UpdateUserExpireTimeTask,这样就能覆盖掉之前定义的Spring boot的定时任务,因为标注了@ConditionalOnMissingBean

为什么中间加一个抽象类AbstractUpdateUserExpireTimeTask 呢?
因为加一个抽象层,能把多个子类中相同的代码,抽出来放到里面去,当然如果不需要注入外部资源,直接写到接口里,作为默认实现也是可以的

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值