李福春 · 2020年01月13日

0113 spring定时任务和异步线程池

0113 spring的异步方法和定时任务

背景

spring的内容比较多,常规的知识必须进行系统化的学习,但是一些边缘的技术点,在实际工作中也是非常适用的;下面一一介绍和实践一次。

异步线程池

场景:下发任务跟执行任务分开。
比如我需要做一个数据统计。
场景常规做法改进做法
计算每天的统计数据,比如日新增,日活跃,日留存等实时计算,计算和获取结果在同一个线程里完成分两个部分:1.触发计算;2.异步完成计算;

spring中如何实现异步计算

  1. 系统中配置异步线程池;
  2. 在系统入口处配置@EnableAsync 注解;
  3. 在业务方法中标注 @Async 注解;

代码实例:

1.配置线程池,开启标记;

package com.springbootpractice.demo.spring.other.config;

import com.google.common.util.concurrent.ThreadFactoryBuilder;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.aop.interceptor.SimpleAsyncUncaughtExceptionHandler;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.EnableAsync;

import java.util.concurrent.Executor;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * 说明:自定义异步线程池和异常处理
 * @author carter
 * 创建时间: 2020年01月13日 10:12 上午
 **/
@EnableAsync
@Configuration
public class AsyncConfig implements AsyncConfigurer {

    /**
     * @return 线程池
     */
    @Override
    public Executor getAsyncExecutor() {
        return new ThreadPoolExecutor(2, 4, 60, TimeUnit.SECONDS,
                new LinkedBlockingDeque<>(10000),
                new ThreadFactoryBuilder().setDaemon(true).setNameFormat("demo_spring_other_%s").build(),
                new ThreadPoolExecutor.DiscardPolicy()
        );
    }

    /**
     * 可以结合监控系统,监控该异常,进行告警
     * @return 异常处理
     */
    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return new SimpleAsyncUncaughtExceptionHandler();
    }
}

2.使用代码

package com.springbootpractice.demo.spring.other.service;

import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import java.time.LocalDate;
import java.util.concurrent.TimeUnit;

/**
 * 说明:统计数据计算业务代码
 * @author carter
 * 创建时间: 2020年01月13日 10:27 上午
 **/
@Service
@Slf4j
public class AsyncSummaryDataService {

    @Async
    public void calculateDayNewData(LocalDate day) {
        try {
            TimeUnit.SECONDS.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        log.info("calculateDayNewData 执行线程:{}", Thread.currentThread().getName());
    }


    @Async
    public void calculateDayLeftData(LocalDate day) {
        try {
            TimeUnit.SECONDS.sleep(20);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.info("calculateDayLeftData 执行线程:{}", Thread.currentThread().getName());

    }

    @Async
    public void calculateDayActiveData(LocalDate day) {
        try {
            TimeUnit.SECONDS.sleep(30);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.info("calculateDayActiveData 执行线程:{}", Thread.currentThread().getName());

    }


}
  1. 测试代码
 @Autowired
    private AsyncSummaryDataService asyncSummaryDataService;

    @Test
    void asyncTest() {

        asyncSummaryDataService.calculateDayActiveData(LocalDate.now());
        asyncSummaryDataService.calculateDayLeftData(LocalDate.now());
        asyncSummaryDataService.calculateDayNewData(LocalDate.now());

    }

输出:


2020-01-13 10:38:02.084  INFO 35059 --- [_spring_other_1] c.s.d.s.o.s.AsyncSummaryDataService      : calculateDayLeftData 执行线程:demo_spring_other_1
2020-01-13 10:38:12.085  INFO 35059 --- [_spring_other_0] c.s.d.s.o.s.AsyncSummaryDataService      : calculateDayActiveData 执行线程:demo_spring_other_0
2020-01-13 10:38:12.092  INFO 35059 --- [_spring_other_1] c.s.d.s.o.s.AsyncSummaryDataService      : calculateDayNewData 执行线程:demo_spring_other_1
2020-01-13 10:38:12.108  INFO 35059 --- [           main] c.s.d.s.o.service.SummaryDataService     : calculateDayActiveData 执行线程:main
2020-01-13 10:38:32.114  INFO 35059 --- [           main] c.s.d.s.o.service.SummaryDataService     : calculateDayLeftData 执行线程:main
2020-01-13 10:38:42.117  INFO 35059 --- [           main] c.s.d.s.o.service.SummaryDataService     : calculateDayNewData 执行线程:main

定时任务

B端应用,可能需要一些定时任务,比如月末报表。在spring中启用定时任务非常简单,步骤如下:

使用步骤

  1. 标准@EnableScheduling 在启动入口处;
  2. 在使用的业务方法上标注@Scheduled注解;

@Scheduled注解的配置内容 

配置项说明
croncron表达式
zone时区
fixedDelay,fixedDelayString固定延迟时间,两个任务之间的执行延迟;_milliseconds_
fixedRate,fixedRateString固定频率,两个任务之间固定的执行间隔
milliseconds
initialDelay,initialDelayString初始化延迟,springIOC初始化完毕之后多少延迟之后开始执行首次任务的延迟
milliseconds

cron表达式不说了,可以直接使用工具生成:http://cron.qqe2.com/ 推荐一个;
实例: 0 0 0 ? 每天0点开始执行;

实例代码:

package com.springbootpractice.demo.spring.other.service;

import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.scheduling.annotation.Schedules;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

/**
 * 说明:TODO
 * @author carter
 * 创建时间: 2020年01月13日 11:14 上午
 **/
@Service
@Slf4j
public class ScheduleTaskService {

    @Schedules({
            @Scheduled(initialDelay = 5 * 1000, fixedRate = 10 * 1000)
    })
    public void generateMonthReport() {
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.info("执行月报表计算");
    }

}

小结

  1. 学会了使用spring内置的异步线程池来把某些同步方法改造成异步方法;
  2. 学会了使用sping内置的定时任务来完成某些工作;
代码点我获取!
原创不易,转载请注明出处。
推荐阅读
关注数
0
文章数
53
爱技术,爱编码,爱生活!
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息