SpringBoot 的@Async的使用和关键字synchronized的使用
多线程
- SpringBoot 的@Async的使用和关键字synchronized的使用
- 前言
- 一、@Async注解创建异步方法的基础使用?
- 注:简单介绍四种写法
- 第一种是异步删除多个表数据
- 第二种是循环内执行多次此方法
- 第三种是异步执行带返回值
- 第四种是异步执行带返回值等待全部线程执行完再做操作
- 二、使用自己指定的线程池
- 三、关键字synchronized的基本使用
- 总结:仰天大笑出门去,我辈岂是蓬蒿人
前言
在Spring4中,Spring中引入了一个新的注解@Async,这个注解让我们在使用Spring完成异步操作变得非常方便,在SpringBoot环境中,要使用@Async注解,需要先在启动类上加上@EnableAsync注解
一、@Async注解创建异步方法的基础使用?
注:简单介绍四种写法
难度从上到下,其实也不难,就是换种写法
第一种是异步删除多个表数据
没什么好说的
第二种是循环内执行多次此方法
比如循环导入数据,大量数据入库多个表
第三种是异步执行带返回值
主线程需要用到异步方法的返回值,返回值可以是任意的,没有限制
第四种是异步执行带返回值等待全部线程执行完再做操作
主线程等待所有子线程执行完毕,再往下去执行
每种方法都跟着步骤去看
1.首先在启动类配置@EnableAsync**
@SpringBootApplication
// 启用异步
@EnableAsync
public class TestApplication {
public static void main(String[] args) {
SpringApplication.run(TestApplication.class, args);
}
}
2.异步方法最好放在一个单独的包下,先创建一个接口类(这里就是一个简单的接口)
public interface AsyncThreadService {
/**
* @Description: [异步删除保护监测监测数据]
* @return: void
* @Author: 杨永卓
* @Date: 2021/12/24 10:06
*/
void deleteMonitorDataBh(Map<String, Object> map);
/**
* @Description: [数据上传与导入异步导入(里程不能为空)]
* @Param: [object:假设这是导入的数据]
* @return: void
* @Author: 杨永卓
* @Date: 2022/1/10 13:57
*/
void addMonitorDataBh(List<Object > object);
}
3.编写异步类(需注意类上加@Service,加上此注解类会自动注入到Spring容器中,方法上加@Async注解,表示这是一个异步类)
@Slf4j
@Service
public class AsyncThreadServiceImpl implements AsyncThreadService {
/**
* @param map
* @Description: [异步删除数据,
* 当执行这个方法时,这个方法每一行的访问数据库都会单独开启一个线程去执行删除]
* @return: void
* @Author: 杨永卓
* @Date: 2021/12/24 10:06
*
*/
@Async
@Override
public void deleteMonitorDataBh(Map<String, Object> map) {
try {
log.info("异步数据删除方法开始执行");
// 用来删除数据表
data1Mapper.deleteByMap(map);
log.info("异步删除第1个表数据根据字段");
data2Mapper.deleteByMap(map);
log.info("异步删除第2个表数据根据字段");
data3Mapper.deleteByMap(map);
log.info("异步删除第3个表数据根据字段");
data4Mapper.deleteByMap(map);
log.info("异步删除第4个表数据根据字段");
log.info("异步数据删除方法执行完成");
} catch (Exception e) {
log.error("删除出现问题");
e.printStackTrace();
//手动回滚数据库操作
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
}
@Async
@Override
public void addMonitorDataBh(iList<Object > object) {
log.info("当前开始导入的数据:{}",object);
}
@Async
public Future<String> returnString1(Object obj){
//假设拿到obj对obj进行处理操作
//执行完成返回一个返回值 String str="执行完成";
return new AsyncResult(str);
}
@Async
public Future<String> returnString2(Object obj){
//假设拿到obj对obj进行处理操作
//执行完成返回一个返回值 String str="执行完成";
return new AsyncResult(str);
}
}
4.业务层调用测试
@Service
@Slf4j
public class TestServiceImpl implements TestService {
//异步
@Autowired
private AsyncThreadService asyncThreadService;
//测试1
@Override
public boolean deleteData(String sign) throws TppException {
Map<String, Object> map = new HashMap<>();
map.put("sign", sign);
//只有这一行会异步执行
asyncThreadService.deleteMonitorDataBh(map);
//下面正常写逻辑代码
//删除关联文件
fileBusinessService.removeByMap(map);
return true;
}
//测试2
@Override
public void addMonitorDataBh(File file) {
// 假设这是一个有10个sheet页的数据流文件,
// 并且每个sheet数据量很大,需要异步导入
//每一个循环都会启动一个线程去执行这个方法
for (int i = 0; i < 10; i++) {
//循环拿到了其中一个sheet数据List<Object > object
asyncThreadService.addMonitorDataBh(object);
}
}
//测试3,不等待全部线程执行完进行下面操作
@Override
public void testReturnStringOne(Object object) {
Future<String> task1=asyncThreadService.returnString1(object);
Future<String> task2=asyncThreadService.returnString2(object);
//get方法是获取返回值的,这里拿到的就是返回的,执行完成这几个字符串
task1.get();
task2.get();
System.out.println(task1.get());
System.out.println(task2.get());
}
//测试4,等待全部线程执行完,再做操作
@Override
public void testReturnStringOne(Object object) {
Future<String> future1=asyncThreadService.returnString1(object);
Future<String> future2=asyncThreadService.returnString2(object);
//执行while,只有两个异步线程执行完,才会跳出循环,进行下面的操作
while (true) {
if (future1.isDone() && future2.isDone()){
break;
}
}
future1.get();
future2.get();
System.out.println(future1.get());
System.out.println(future2.get());
}
}
二、使用自己指定的线程池
也适用上面那种异步写法,多写一种写法更好地提升自己
1、新建一个配置类
@Configuration
@EnableAsync
public class ThreadPoolTaskConfig {
/**
* 默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,
* 当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中;
* 当队列满了,就继续创建线程,当线程数量大于等于maxPoolSize后,开始使用拒绝策略拒绝
*/
/** 核心线程数(默认线程数) */
private static final int corePoolSize = 20;
/** 最大线程数 */
private static final int maxPoolSize = 100;
/** 允许线程空闲时间(单位:默认为秒) */
private static final int keepAliveTime = 10;
/** 缓冲队列大小 */
private static final int queueCapacity = 200;
/** 线程池名前缀 */
private static final String threadNamePrefix = "Async-Service-";
@Bean("taskExecutor") // bean的名称,默认为首字母小写的方法名
public ThreadPoolTaskExecutor taskExecutor(){
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(corePoolSize);
executor.setMaxPoolSize(maxPoolSize);
executor.setQueueCapacity(queueCapacity);
executor.setKeepAliveSeconds(keepAliveTime);
executor.setThreadNamePrefix(threadNamePrefix);
// 线程池对拒绝任务的处理策略
// CallerRunsPolicy:由调用线程(提交任务的线程)处理该任务
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
// 初始化
executor.initialize();
return executor;
}
}
2.创建两个异步方法类
@Service
public class AsyncService {
@Async("taskExecutor")
public void sendMessage1() throws InterruptedException {
Thread.sleep(5000); // 模拟耗时
}
@Async("taskExecutor")
public void sendMessage2() throws InterruptedException {
Thread.sleep(2000); // 模拟耗时
}
}
3.业务类模拟调用
@Service
public class TaskServic {
@Autowired
private AsyncServicet asyncService;
// 订单处理任务
public void orderTask() throws InterruptedException {
this.run(); // 先执行这里面的代码
asyncService.sendMessage1(); // 执行这个方法会睡5秒
asyncService.sendMessage2(); // 执行这个方法会睡2秒
}
// 假设这是一大段业务代码
public void run(){
System.out.println("业务代码1");
System.out.println("业务代码2 ");
}
}
执行结果:sendMessage2会先执行完,然后才是sendMessage1执行完
三、关键字synchronized的基本使用
使用这个关键字可以保证执行的方法和类在同一时间内只允许一个线程去访问,适用那些
对数据库进行增删改操作的逻辑代码,以免多个线程同事访问出现脏数据,使用也很简单
只需要在方法上添加这个关键字即可。
/**
* @Description: [同步方法:修改数据库当前列表数据信息]
* @Param: [object:假设是需要操作对应数据库的数据]
* @return: void
* @Author: 杨永卓
* @Date: 2022/1/12 14:10
*/
private synchronized void updateDataBase(Object object) {
//对数据库的操作
}
总结:仰天大笑出门去,我辈岂是蓬蒿人