Springboot联结万物学习笔记--Springboot微服务基础搭建篇(四)--SpringBoot中的异步处理
博客说明:撰写博客目的是在记录自己所学知识、在工作中使用技术遇到的技术问题、一些技术感悟,因此避免不了涉及到和其他文章有相似之处。本文从作者自己的实践中指出相关踩坑问题,着重指出学习过程中遇到的相关问题。如果存在相关侵权问题请联系博主删除,同时有技术上的见解可以在评论去里发出,会不定期回复,谢谢。
gitee地址:https://gitee.com/woniurunfast/springbootwitheverything
01背景
在SpringBoot的日常开发中,一般都是同步调用的,但经常有特殊业务需要做异步来处理
02目标
1、springboot的异步方法介绍
2、优化方法,自定义异步类
03注解调用异步方法
利用springboot提供的@Async完成异步
举例:
场景:假设新蜗牛报道后需要激活学生卡以及学生信息生效
控制层:
package com.hkx.demo.controller;
import com.hkx.demo.service.RegService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/reg")
@Slf4j
public class RegController {
@Autowired
private RegService regService;
@GetMapping("/add")
public String reguser(){
// 1: 注册用户
log.info("新蜗牛报道");
//userService.save(woniu);
// 2: 发送短信
log.info("饭卡激活");
regService.sendMsg();
// 3: 添加积分
log.info("学生信息生效");
regService.addScore();
return "ok";
}
}
service层:
package com.hkx.demo.service;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
@Service
@Slf4j
public class RegService {
// 发送短信,用异步进行处理和标记
@Async
public void sendMsg(){
// todo :模拟耗时5秒
try {
Thread.sleep(5000);
log.info("---------------饭卡激活--------");
}catch (Exception ex){
ex.printStackTrace();
}
}
// 添加积分,用异步进行处理和标记
@Async
public void addScore(){
// todo :模拟耗时5秒
try {
Thread.sleep(5000);
log.info("---------------学生信息生效--------");
}catch (Exception ex){
ex.printStackTrace();
}
}
}
缺点:线程不重用,每次调用都会新建一个新的线程。
04异步线程池的优化
@Async注解异步框架提供多种线程机制:
- SimpleAsyncTaskExecutor:简单的线程池,这个类不重用线程,每次调用都会创建一个新的线程。
- SyncTaskExecutor:这个类没实现异步调用,只是一个同步操作,只适合用于不需要多线程的地方。
- ConcurrentTaskExecutor:Executor的适配类,不推荐使用.。
- ThreadPoolTaskScheduler:可以和cron表达式使用。
- ThreadPoolTaskExecutor:最常用,推荐,其本质就是:java.util.concurrent.ThreadPoolExecutor的包装
在conf包里创建线程配置类SyncThreadPoolConfiguration
package com.hkx.demo.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.ThreadPoolExecutor;
@Configuration
public class SyncThreadPoolConfiguration {
@Bean(name = "ThreadPoolTaskExecutor")
public ThreadPoolTaskExecutor getThreadPoolTaskExecutor(){
ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
// 1: 创建核心线程数 cpu核数 -- 50
threadPoolTaskExecutor.setCorePoolSize(10);
// 2:线程池维护线程的最大数量,只有在缓存队列满了之后才会申请超过核心线程数的线程
threadPoolTaskExecutor.setMaxPoolSize(100);
// 3:缓存队列 可以写大一点无非就浪费一点内存空间
threadPoolTaskExecutor.setQueueCapacity(200);
// 4:线程的空闲事件,当超过了核心线程数之外的线程在达到指定的空闲时间会被销毁 200ms
threadPoolTaskExecutor.setKeepAliveSeconds(200);
// 5:异步方法内部线的名称
threadPoolTaskExecutor.setThreadNamePrefix("woniu-thread-");
// 6:缓存队列的策略 多线程 JUC并发
/* 当线程的任务缓存队列已满并且线程池中的线程数量已经达到了最大连接数,如果还有任务来就会采取拒绝策略,
* 通常有四种策略:
*ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出异常:RejectedExcutionException异常
*ThreadPoolExecutor.DiscardPolicy:丢弃任务,但是不抛出异常
*ThreadPoolExecutor.DiscardOldestPolicy: 丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
*ThreadPoolExecutor.CallerRunsPolicy:重试添加当前的任务,自动重复调用execute()方法,直到成功。
*ThreadPoolExecutor. 扩展重试3次,如果3次都不充公在移除。
*jmeter 压力测试 1s=500
* */
threadPoolTaskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
threadPoolTaskExecutor.initialize();
return threadPoolTaskExecutor;
}
}
那么@Async会按照上述配置执行异步线程:
结果: