SpringBoot定时任务和异步操作

SpringBoot定时任务和异步操作

日常求赞,感谢老板。

欢迎关注公众号:其实是白羊。干货持续更新中......

image

一、定时任务

在做业务时总会有这样的场景:在特定时间去执行某些逻辑。这其实就是定时任务的应用场景,比如:需要每月一日给用户发上月数据总结等场景。

1.技术

实现定时任务的技术很多

  • Timer:JDK自带的java.util.Timer其实更类似于定时器,可实现延迟执行和按照一定频率执行,也可以指定某个时间执行,使用较少
  • ScheduledExecutorService:也是JDK自带的,是基于线程池设计的定时任务类,根据Executors创建时的线程数量去执行具体任务(多个线程数量就是每个人物分配一个线程)
  • Spring Task:即今天要介绍了主角,是Spring自带的,当然这里通过Springboot使用。
  • Quartz:开源的调度框架,功能更加强大

2.注解使用

1)@EnableScheduling

开启定时任务,可标注在启动类或者任何配置类上(能扫描到对象上都可以)

2)@Scheduled

配置具体的任务执行规则,可标注在能被扫描的类的方法上,属性有:

  • fixedRate:上一个任务开始时间和下一个任务开始时间的时间间隔(毫秒)
  • fixedDelay:上一个任务的结束时间和下一个任务的开始时间的时间间隔(毫秒)
  • initialDelay:第一次执行延迟执行的时间(毫秒)
  • cron:通过cron表达式来配置执行时机

3.cron表达式

定时任务的场景:延迟执行、一定频率执行、指定时间执行

这里的cron表达式即为了描述任务执行的时间规则。cron由6-7个元素组成,他们之间使用空格来分割,以此代表:

  • 秒:0~59
  • 分:0~59
  • 时:0~23
  • 日:1~31(具体月的最后一天也可能是30)
  • 月:1~12
  • 星期:1~7(注意1为周日)
  • 年:1970~2099

除了上面的数字元素值还有下面几个特殊的元素值:

  • *:表示任意一个值都会触发,七个元素位置中都可以出现,如在分钟出现即表示每分钟都回去执行
  • ?:和*类似,但只会出现在日和星期(这两个位置只能有一个是具有意义的描述,如:要么是每个月的3号要么是每个月的每个星期的周三),当其中一个配置了有意义的值,另一个写?
  • -:表示范围(至),七个元素位置中都可以出现,如分钟里出现1-7则表示1分钟到7分钟每分钟执行一次
  • /:表示从开始时间每隔多长时间执行一次,七个元素位置中都可以出现,如分钟里出现1/7则表示1分触发一次1+7=8分触发一次
  • ,:表示枚举值,七个元素位置中都可以出现,如分钟里出现1,7则表示1分和7分各执行一次
  • L:表示最后,只会出现在日和星期位置,日表示最后一天,星期则会搭配数字如5L表示最后一个周四
  • W:表示有效工作日(周一到周五),只会出现在日期里,前面会搭配数字,如5W:如果5号是周六那么执行时间为周五即4号;如果5号是周日那么执行时间为下周一即6号;(即系统会推荐离今天最近的一个工作日,但注意不会进行跨越查找:如果是31W且31号是周日,那么会在29号执行)
  • LW:连用表示最后一个工作日,只会出现在日期里
  • “#”:(前后要加数字)表示第几个星期几,只会出现在星期里,如4#2 即表示第二个周三(前面表示周几,后面表示第几个)

了解了上面的表达式规则就可以写出满足条件的cron表达式了,建议多设计几个场景练习下

4.单线程和多线程执行

如果就按上面的配置写好任务A(fixedRate=2000)和任务B(fixedRate=3000)直接启动执行的话,会存在以下问题:

  • 如果A1任务执行时间超过2s那么原定于A1任务开始时2s后执行的任务A2就不能按时执行
  • 任务A和任务B要交替执行

上面的问题都不能让我们的任务按照各自规定的执行计划去执行,归根到底还是所有的任务都在一个线程里进行,所以做不到异步的并发执行,那这是就可以通过多线程来实现各个任务之间异步的并发执行

这里我们可以使用Spring的@Async注解来实现异步

二、异步

SpringBoot同样支持@Async来实现异步(增加了自动配置可直接使用)

1.注解

1)@EnableAsync

标注在启动类或配置类上,表是开启异步

2)@Async

可标注在类上(能被spring容器扫描到的类上)或方法上

标注在类上则这个类里的方法都被表示为异步

2.异步方法两种返回值

  1. 不需要返回值:void

  2. 需要返回值:

     @Async  
        public Future<String> doTaskOne() throws Exception {
            return new AsyncResult<>("任务一完成");  
        }  
    

3.自动配置

使用@Async来实现异步,其底层还是使用了多线程来进行实现的,那么这个多线程或者线程池是在哪里设置或配置的呢?我们都直到SpringBoot加入了大量的自动配置,我们在spring-boot-autoconfigure包下面可以找到task包下的TaskExecutionAutoConfiguration类中:

@Lazy
@Bean(name = { APPLICATION_TASK_EXECUTOR_BEAN_NAME,
            AsyncAnnotationBeanPostProcessor.DEFAULT_TASK_EXECUTOR_BEAN_NAME })
@ConditionalOnMissingBean(Executor.class)
//这个注解的意思:当在容器中没有发现Executor这个类则会加载这个bean,可以理解为此处为默认缺省对象
//返回的是一个spring为我们提供的线程池
    public ThreadPoolTaskExecutor applicationTaskExecutor(TaskExecutorBuilder builder) {
        return builder.build();
    }

找到TaskExecutorBuilder:

@Bean
    @ConditionalOnMissingBean
    public TaskExecutorBuilder taskExecutorBuilder() {
        TaskExecutionProperties.Pool pool = this.properties.getPool();
        TaskExecutorBuilder builder = new TaskExecutorBuilder();
        builder = builder.queueCapacity(pool.getQueueCapacity());
        builder = builder.corePoolSize(pool.getCoreSize());
        builder = builder.maxPoolSize(pool.getMaxSize());
        builder = builder.allowCoreThreadTimeOut(pool.isAllowCoreThreadTimeout());
        builder = builder.keepAlive(pool.getKeepAlive());
        builder = builder.threadNamePrefix(this.properties.getThreadNamePrefix());
        builder = builder.customizers(this.taskExecutorCustomizers.orderedStream()::iterator);
        builder = builder.taskDecorator(this.taskDecorator.getIfUnique());
        return builder;
    }

这里的properties是在TaskExecutionProperties加载进来的,默认的参数:

private int queueCapacity = Integer.MAX_VALUE;
private int coreSize = 8; 
private int maxSize = Integer.MAX_VALUE;
private boolean allowCoreThreadTimeout = true;

根据下面可知,也可以从yml/properties文件中自定义配置

@ConfigurationProperties("spring.task.execution")

除了上面的方法我们还可以定义自己的Bean来替换默认缺省Bean,根据@ConditionalOnMissingBean(Executor.class)可知,我们只需要在配置类里加上:

@Bean
    public Executor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(核心线程数量);
        executor.setMaxPoolSize(最大线程数量);
        executor.setQueueCapacity(任务队列大小);
        executor.initialize();
        return executor;
    }

三、最后

根据上面的介绍,要实现异步的定时任务就可以展开,任务方法上面加上@Async来实现

点个赞啊亲

如果你认为本文对你有帮助,可以「在看/转发/赞/star」,多谢

如果你还发现了更好或不同的想法,还可以在留言区一起探讨下


欢迎关注公众号:「其实是白羊」干货持续更新中......

image
image
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,271评论 5 466
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,725评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,252评论 0 328
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,634评论 1 270
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,549评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,985评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,471评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,128评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,257评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,233评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,235评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,940评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,528评论 3 302
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,623评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,858评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,245评论 2 344
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,790评论 2 339