前言
在开发过程中,我们会遇到很多使用线程池的业务场景,例如定时任务使用的就是ScheduledThreadPoolExecutor。而有些时候使用线程池的场景就是会将一些可以进行异步操作的业务放在线程池中去完成,例如在生成订单的时候给用户发送短信,生成订单的结果不应该被发送短信的成功与否所左右,也就是说生成订单这个主操作是不依赖于发送短信这个操作,所以我们就可以把发送短信这个操作置为异步操作。而要想完成异步操作,一般使用的一个是消息服务器MQ,一个就是线程池。今天我们就来看看在Java中常用的Spring框架中如何去使用线程池来完成异步操作,以及分析背后的原理。

在Spring4中,Spring中引入了一个新的注解@Async,这个注解让我们在使用Spring完成异步操作变得非常方便。

在SpringBoot环境中,要使用@Async注解,我们需要先在启动类上加上@EnableAsync注解。这个与在SpringBoot中使用@Scheduled注解需要在启动类中加上@EnableScheduling是一样的道理(当然你使用古老的XML配置也是可以的,但是在SpringBoot环境中,建议的是全注解开发),具体原理下面会分析。加上@EnableAsync注解后,如果我们想在调用一个方法的时候开启一个新的线程开始异步操作,我们只需要在这个方法上加上@Async注解,当然前提是,这个方法所在的类必须在Spring环境中。

项目实况介绍

项目中,我需要将700w条数据,定时任务加入到mysql表中,去掉日志打印和一些其他因素的影响,入库时间还是需要8个小时以上,严重影响后续的一系列操作,所以我才用@Async注解,来实现异步入库,开了7个线程,入库时间缩短为1.5个小时,大大提高效率,以下是详细介绍,一级一些需要注意的坑.

需要写个配置文件两种方式

第一种方式

@Configuration
@EnableAsync //启用异步任务
public class ThreadConfig {
@Bean
public ThreadPoolTaskExecutor executor(){
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
//配置核心线程数
executor.setCorePoolSize(15);
//配置最大线程数
executor.setMaxPoolSize(30);
//配置队列大小
executor.setQueueCapacity(1000);
//线程的名称前缀
executor.setThreadNamePrefix("Executor-");
//线程活跃时间(秒)
//executor.setKeepAliveSeconds(60);
//等待所有任务结束后再关闭线程池
executor.setWaitForTasksToCompleteOnShutdown(true);
//设置拒绝策略
//executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
//执行初始化
executor.initialize();
return executor;
}
}

第二种方式

@Configuration
@EnableAsync
public class ExecutorConfig { @Value("${thread.maxPoolSize}")
private Integer maxPoolSize;
@Value("${thread.corePoolSize}")
private Integer corePoolSize;
@Value("${thread.keepAliveSeconds}")
private Integer keepAliveSeconds;
@Value("${thread.queueCapacity}")
private Integer queueCapacity;
@Bean
public ThreadPoolTaskExecutor asyncExecutor(){
ThreadPoolTaskExecutor taskExecutor=new ThreadPoolTaskExecutor();
taskExecutor.setCorePoolSize(corePoolSize);//核心数量
taskExecutor.setMaxPoolSize(maxPoolSize);//最大数量
taskExecutor.setQueueCapacity(queueCapacity);//队列
taskExecutor.setKeepAliveSeconds(keepAliveSeconds);//存活时间
taskExecutor.setWaitForTasksToCompleteOnShutdown(true);//设置等待任务完成后线程池再关闭
taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());//设置拒绝策略
taskExecutor.initialize();//初始化
return taskExecutor;
}
}

配置文件

#线程池
thread:
corePoolSize: 5
maxPoolSize: 10
queueCapacity: 100
keepAliveSeconds: 3000

springboot默认是不开启异步注解功能的,所以,要让springboot中识别@Async,则必须在入口文件中,开启异步注解功能

package com.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync; //开启异步注解功能
@EnableAsync
@SpringBootApplication
public class SpringbootTaskApplication { public static void main(String[] args) {
SpringApplication.run(SpringbootTaskApplication.class, args);
} }

这里有个坑!

如果遇到报错:需要加上    proxyTargetClass = true

The bean 'xxxService' could not be injected as a'com.xxxx.xxx.xxxService' because it is a JDK dynamic proxy that implements:
xxxxxx
Action:
Consider injecting the bean as one of its interfaces orforcing the use of CGLib-based proxiesby setting proxyTargetClass=true on @EnableAsync and/or @EnableCaching.
package com.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync; //开启异步注解功能
@EnableAsync(proxyTargetClass = true)
@SpringBootApplication
public class SpringbootTaskApplication { public static void main(String[] args) {
SpringApplication.run(SpringbootTaskApplication.class, args);
} }

当我service层处理完逻辑,吧list分成7个小list然后调用异步方法(异步方法的参数不用管,没影响,只截取核心代码)

List<List<DistributedPredictDTO>> partition = Lists.partition(userList, userList.size() / 7);
for (List<DistributedPredictDTO> distributedPredictDTOS : partition) {
       //调用异步方法
threadService.getI(beginDate, endDate, tableName, distributedPredictDTOS, hMap, i);
}
@Slf4j
@Service
public class ThreadServiceImpl {
@Resource
ResourcePoolUrlProperties properties;
@Resource
private MonitorDao monitorDao;
@Async
Integer getI(String beginDate, String endDate, String tableName, List<DistributedPredictDTO> userList, Map<String, String> hMap, int i) {
log.info("我开始执行");
for (DistributedPredictDTO e : userList) {
String responseStr;
HashMap<String, String> pMap = Maps.newHashMap();
pMap.put("scheduleId", e.getScheduleId());
pMap.put("scheduleName", e.getScheduleName());
pMap.put("distribsunStationId", e.getLabel());
pMap.put("distribsunStationName", e.getValue());
pMap.put("beginTime", beginDate);
pMap.put("endTime", endDate);
try {
if ("180".equals(properties.getNewPowerSys().getDistributedPredictUrl().substring(17, 20))) {
pMap = null;
}
responseStr = HttpClientUtil.doPost(properties.getNewPowerSys().getDistributedPredictUrl(), hMap, pMap);
} catch (Exception exception) {
throw new RuntimeException(e.getValue() + "的功率预测接口异常" + hMap + pMap);
}
if (org.springframework.util.StringUtils.isEmpty(responseStr)) {
log.info(e + "数据为空");
continue;
}
JSONObject resJson = JSONObject.parseObject(responseStr);
JSONObject obj = (JSONObject) resJson.get("obj");
JSONArray tableData = (JSONArray) obj.get("tabledata"); final List<DistributedUserPower> userPowers = Lists.newArrayList();
for (Object o : tableData) {
final DistributedUserPower distributedUserPower = new DistributedUserPower();
distributedUserPower.setData(((JSONObject) o).get("data").toString());
distributedUserPower.setData2(((JSONObject) o).get("data2").toString());
distributedUserPower.setDataTime(((JSONObject) o).get("time").toString());
distributedUserPower.setUserId(e.getLabel());
distributedUserPower.setUserName(e.getValue());
distributedUserPower.setAreaName(e.getScheduleName());
distributedUserPower.setCreateTime(DateUtils.getDate());
userPowers.add(distributedUserPower);
}
monitorDao.saveBatch(userPowers, tableName);
i++;
}
return i;
}

这里有两个坑!

第一个坑:

  我调用的异步方法在当前类中,则直接导致

@Async注解失效
正确操作,异步方法不要和同步调用方法写在同一个类中,应该重新调用其他类

第二个坑:

如果出现这个报错:

Null return value from advice does not mat

问题分析

代码中采用异步调用,AOP 做来一层切面处理,底层是通过 JDK 动态代理实现

不管采用 JDK 还是 CGLIB 代理,返回值必须是包装类型,所以才会导致上诉的报错信息

处理方案
将异步方法的返回值修改为基本类型的对应包装类型即可,如 int -> Integer

5分钟测试效果图:

最后一张是7线程:

spring-boot @Async注解 解决异步多线程入库的问题的更多相关文章

  1. 使用Spring中@Async注解实现异步调用

    异步调用? 在解释异步调用之前,我们先来看同步调用的定义:同步就是整个处理过程顺序执行,当各个过程都执行完毕,并返回结果. 异步调用则是只是发送了调用的指令,调用者无需等待被调用的方法完全执行完毕,继 ...

  2. Spring使用@Async注解

    本文讲述@Async注解,在Spring体系中的应用.本文仅说明@Async注解的应用规则,对于原理,调用逻辑,源码分析,暂不介绍.对于异步方法调用,从Spring3开始提供了@Async注解,该注解 ...

  3. Spring boot 使用WebAsyncTask处理异步任务

    上文介绍了基于 @Async 注解的 异步调用编程,本文将继续引入 Spring Boot 的 WebAsyncTask 进行更灵活异步任务处理,包括 异步回调,超时处理 和 异常处理. 正文 1. ...

  4. Spring Boot常用注解总结

    Spring Boot常用注解总结 @RestController和@RequestMapping注解 @RestController注解,它继承自@Controller注解.4.0之前的版本,Spr ...

  5. Spring Boot 常用注解汇总

    一.启动注解 @SpringBootApplication @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documen ...

  6. 3个Spring Boot核心注解,你知道几个?

    Spring Boot 核心注解讲解 Spring Boot 最大的特点是无需 XML 配置文件,能自动扫描包路径装载并注入对象,并能做到根据 classpath 下的 jar 包自动配置. 所以 S ...

  7. Spring Boot@Component注解下的类无法@Autowired的问题

    title: Spring Boot@Component注解下的类无法@Autowired的问题 date: 2019-06-26 08:30:03 categories: Spring Boot t ...

  8. Spring boot 基于注解方式配置datasource

    Spring boot 基于注解方式配置datasource 编辑 ​ Xml配置 我们先来回顾下,使用xml配置数据源. 步骤: 先加载数据库相关配置文件; 配置数据源; 配置sqlSessionF ...

  9. 【SpringBoot】15. Spring Boot核心注解

    Spring Boot核心注解 1 @SpringBootApplication 代表是Spring Boot启动的类 2 @SpringBootConfiguration 通过bean对象来获取配置 ...

随机推荐

  1. 饿了么组件库element-ui正则表达式验证表单,后端验证表单。

    前言 老是遇到一些朋友问一些element-ui组件使用相关的基础问题,因为官方文档上并没有提供所有琐碎的功能代码demo.从这里开始我会根据我实际遇到的问题记录一些常见的官方文档没有详述的功能代码, ...

  2. 如何形成一个完整的HTML对象

    写在前面,本文将同步发布于Blog.掘金.segmentfault.知乎等处,如果本文对你有帮助,记得为我得到我的个人技术博客项目给个star哦. 为何写这篇文章? 你可能做Web开发已经有一段时间, ...

  3. vue常用知识点总结

    感谢本文引用链接的各位大佬们,小菜鸟我只是个搬运工 1.谈一谈你理解的vue是什么样子的? vue是数据.视图分离的一个框架,让数据与视图间不会发生直接联系.MVVM 组件化:把整体拆分为各个可以复用 ...

  4. java中时间的规范是按美国,SimpleDateFormat怎么处理

    题目3.2: 如果时间的规范是按美国,怎么处理? import java.text.ParseException;import java.text.SimpleDateFormat;import ja ...

  5. 如何基于 ZEGO SDK 实现 Flutter 一对一音视频聊天应用?

    之前的文章发布了ZEGO SDK实现Android端音视频通话应用的开发教程,不少开发者反馈很实用,能不能也出一版Flutter的教程. 有求必应,这不小编来了- 我们封装了ZEGO Flutter ...

  6. Mybatis多表查询出现null字段

    写在前面 今天使用mybatis实现多表查询,记录一下其中遇到的坑 mybatis多表查询简介 mybatis多表查询主要有两个方式,通俗易懂的来说就是一个是查询少量属性(association),一 ...

  7. MFC---典型类和函数

    在MFC中,典型的类有CString.CRect.CDialog等,这些类的使用方法是通用的,下文以CString类的使用为例做一个详细说明.类的使用主要还是使用类的方法,可以查看类的定义,查看这个类 ...

  8. 网络协议之:socket协议详解之Unix domain Socket

    目录 简介 什么是Unix domain Socket 使用socat来创建Unix Domain Sockets 使用ss命令来查看Unix domain Socket 使用nc连接到Unix do ...

  9. 解决关于ARM_MATH数学库宏定义的报错

    昨天在建立新工程的时候发现加入含有ARM_MATH库的时候出现了宏定义报错. #error directive:"Define according the used Cortex core ...

  10. 【FAQ】应用集成HMS Core部分服务出现“ 6003报错”情况的解决方法来啦

    背景 开发者在应用中集成HMS Core部分服务时,android sdk 以及flutter等跨平台sdk,会出现编译打包后,运行报6003错误码的情况.根据查询可以得知,错误代码 6003 表示证 ...