0113 spring的异步方法和定时任务
背景
spring的内容比较多,常规的知识必须进行系统化的学习,但是一些边缘的技术点,在实际工作中也是非常适用的;下面一一介绍和实践一次。
异步线程池
场景:下发任务跟执行任务分开。
比如我需要做一个数据统计。
场景 | 常规做法 | 改进做法 |
---|---|---|
计算每天的统计数据,比如日新增,日活跃,日留存等 | 实时计算,计算和获取结果在同一个线程里完成 | 分两个部分:1.触发计算;2.异步完成计算; |
spring中如何实现异步计算
- 系统中配置异步线程池;
- 在系统入口处配置@EnableAsync 注解;
- 在业务方法中标注 @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());
}
}
- 测试代码
@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中启用定时任务非常简单,步骤如下:
使用步骤
- 标准@EnableScheduling 在启动入口处;
- 在使用的业务方法上标注@Scheduled注解;
配置项 | 说明 |
---|---|
cron | cron表达式 |
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("执行月报表计算");
}
}
小结
- 学会了使用spring内置的异步线程池来把某些同步方法改造成异步方法;
- 学会了使用sping内置的定时任务来完成某些工作;
代码点我获取!
原创不易,转载请注明出处。