spring?boot使用@Async注解解決異步多線程入庫(kù)的問(wèn)題
前言
在開(kāi)發(fā)過(guò)程中,我們會(huì)遇到很多使用線程池的業(yè)務(wù)場(chǎng)景,例如定時(shí)任務(wù)使用的就是ScheduledThreadPoolExecutor。而有些時(shí)候使用線程池的場(chǎng)景就是會(huì)將一些可以進(jìn)行異步操作的業(yè)務(wù)放在線程池中去完成,例如在生成訂單的時(shí)候給用戶發(fā)送短信,生成訂單的結(jié)果不應(yīng)該被發(fā)送短信的成功與否所左右,也就是說(shuō)生成訂單這個(gè)主操作是不依賴于發(fā)送短信這個(gè)操作,所以我們就可以把發(fā)送短信這個(gè)操作置為異步操作。而要想完成異步操作,一般使用的一個(gè)是消息服務(wù)器MQ,一個(gè)就是線程池。今天我們就來(lái)看看在Java中常用的Spring框架中如何去使用線程池來(lái)完成異步操作,以及分析背后的原理。
在Spring4中,Spring中引入了一個(gè)新的注解@Async,這個(gè)注解讓我們?cè)谑褂肧pring完成異步操作變得非常方便。
在SpringBoot環(huán)境中,要使用@Async注解,我們需要先在啟動(dòng)類上加上@EnableAsync注解。這個(gè)與在SpringBoot中使用@Scheduled注解需要在啟動(dòng)類中加上@EnableScheduling是一樣的道理(當(dāng)然你使用古老的XML配置也是可以的,但是在SpringBoot環(huán)境中,建議的是全注解開(kāi)發(fā)),具體原理下面會(huì)分析。加上@EnableAsync注解后,如果我們想在調(diào)用一個(gè)方法的時(shí)候開(kāi)啟一個(gè)新的線程開(kāi)始異步操作,我們只需要在這個(gè)方法上加上@Async注解,當(dāng)然前提是,這個(gè)方法所在的類必須在Spring環(huán)境中。
項(xiàng)目實(shí)況介紹
項(xiàng)目中,我需要將700w條數(shù)據(jù),定時(shí)任務(wù)加入到mysql表中,去掉日志打印和一些其他因素的影響,入庫(kù)時(shí)間還是需要8個(gè)小時(shí)以上,嚴(yán)重影響后續(xù)的一系列操作,所以我才用@Async注解,來(lái)實(shí)現(xiàn)異步入庫(kù),開(kāi)了7個(gè)線程,入庫(kù)時(shí)間縮短為1.5個(gè)小時(shí),大大提高效率,以下是詳細(xì)介紹,一級(jí)一些需要注意的坑.
需要寫(xiě)個(gè)配置文件兩種方式
第一種方式
@Configuration @EnableAsync //啟用異步任務(wù) public class ThreadConfig { @Bean public ThreadPoolTaskExecutor executor(){ ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); //配置核心線程數(shù) executor.setCorePoolSize(15); //配置最大線程數(shù) executor.setMaxPoolSize(30); //配置隊(duì)列大小 executor.setQueueCapacity(1000); //線程的名稱前綴 executor.setThreadNamePrefix("Executor-"); //線程活躍時(shí)間(秒) //executor.setKeepAliveSeconds(60); //等待所有任務(wù)結(jié)束后再關(guān)閉線程池 executor.setWaitForTasksToCompleteOnShutdown(true); //設(shè)置拒絕策略 //executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); //執(zhí)行初始化 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);//核心數(shù)量 taskExecutor.setMaxPoolSize(maxPoolSize);//最大數(shù)量 taskExecutor.setQueueCapacity(queueCapacity);//隊(duì)列 taskExecutor.setKeepAliveSeconds(keepAliveSeconds);//存活時(shí)間 taskExecutor.setWaitForTasksToCompleteOnShutdown(true);//設(shè)置等待任務(wù)完成后線程池再關(guān)閉 taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());//設(shè)置拒絕策略 taskExecutor.initialize();//初始化 return taskExecutor; } }
配置文件
#線程池 thread: corePoolSize: 5 maxPoolSize: 10 queueCapacity: 100 keepAliveSeconds: 3000
springboot默認(rèn)是不開(kāi)啟異步注解功能的,所以,要讓springboot中識(shí)別@Async,則必須在入口文件中,開(kāi)啟異步注解功能
package com.demo; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.scheduling.annotation.EnableAsync; //開(kāi)啟異步注解功能 @EnableAsync @SpringBootApplication public class SpringbootTaskApplication { public static void main(String[] args) { SpringApplication.run(SpringbootTaskApplication.class, args); } }
這里有個(gè)坑!
如果遇到報(bào)錯(cuò):需要加上 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.
當(dāng)我service層處理完邏輯,吧list分成7個(gè)小list然后調(diào)用異步方法(異步方法的參數(shù)不用管,沒(méi)影響,只截取核心代碼)
List<List<DistributedPredictDTO>> partition = Lists.partition(userList, userList.size() / 7); for (List<DistributedPredictDTO> distributedPredictDTOS : partition) { //調(diào)用異步方法 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("我開(kāi)始執(zhí)行"); 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() + "的功率預(yù)測(cè)接口異常" + hMap + pMap); } if (org.springframework.util.StringUtils.isEmpty(responseStr)) { log.info(e + "數(shù)據(jù)為空"); 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; }
這里有兩個(gè)坑!
第一個(gè)坑:
我調(diào)用的異步方法在當(dāng)前類中,則直接導(dǎo)致
@Async注解失效
正確操作,異步方法不要和同步調(diào)用方法寫(xiě)在同一個(gè)類中,應(yīng)該重新調(diào)用其他類
第二個(gè)坑:
如果出現(xiàn)這個(gè)報(bào)錯(cuò):
Null return value from advice does not mat
問(wèn)題分析
代碼中采用異步調(diào)用,AOP 做來(lái)一層切面處理,底層是通過(guò) JDK 動(dòng)態(tài)代理實(shí)現(xiàn)
不管采用 JDK 還是 CGLIB 代理,返回值必須是包裝類型,所以才會(huì)導(dǎo)致上訴的報(bào)錯(cuò)信息
處理方案
將異步方法的返回值修改為基本類型的對(duì)應(yīng)包裝類型即可,如 int -> Integer
5分鐘測(cè)試效果圖:
最后一張是7線程:
總結(jié)
到此這篇關(guān)于spring boot使用@Async注解解決異步多線程入庫(kù)問(wèn)題的文章就介紹到這了,更多相關(guān)springboot @Async異步多線程入庫(kù)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- SpringCloud解決Feign異步回調(diào)問(wèn)題(SpringBoot+Async+Future實(shí)現(xiàn))
- Springboot?配置線程池創(chuàng)建線程及配置?@Async?異步操作線程池詳解
- 詳解springboot通過(guò)Async注解實(shí)現(xiàn)異步任務(wù)及回調(diào)的方法
- Spring Boot之@Async異步線程池示例詳解
- SpringBoot異步使用@Async的原理以及線程池配置詳解
- 使用Spring開(kāi)啟@Async異步方式(javaconfig配置)
- Spring里的Async注解實(shí)現(xiàn)異步操作的方法步驟
- 詳解Spring/Spring boot異步任務(wù)編程WebAsyncTask
- Spring中使用Async進(jìn)行異步功能開(kāi)發(fā)實(shí)戰(zhàn)
相關(guān)文章
Java微服務(wù)實(shí)戰(zhàn)項(xiàng)目尚融寶接口創(chuàng)建詳解
這篇文章主要介紹了Java微服務(wù)實(shí)戰(zhàn)項(xiàng)目尚融寶的接口創(chuàng)建流程,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-08-08Java Web程序中利用Spring框架返回JSON格式的日期
這里我們來(lái)介紹一下Java Web程序中利用Spring框架返回JSON格式的日期的方法,前提注意使用@DatetimeFormat時(shí)要引入一個(gè)類庫(kù)joda-time-版本.jar,否則會(huì)無(wú)法訪問(wèn)相應(yīng)路徑2016-05-05PowerJob的OhMyClassLoader工作流程源碼解讀
這篇文章主要介紹了PowerJob的OhMyClassLoader工作流程源碼解讀,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2024-01-01Java Spring數(shù)據(jù)單元配置過(guò)程解析
這篇文章主要介紹了Java Spring數(shù)據(jù)單元配置過(guò)程解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-12-12解決MyBatis @param注解參數(shù)類型錯(cuò)誤異常的問(wèn)題
這篇文章主要介紹了解決MyBatis @param注解參數(shù)類型錯(cuò)誤異常的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-02-02IntelliJ IDEA設(shè)置顯示內(nèi)存指示器和設(shè)置內(nèi)存大小的方法
這篇文章主要介紹了IntelliJ IDEA設(shè)置顯示內(nèi)存指示器和設(shè)置內(nèi)存大小的方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-04-04使用Spring?AOP實(shí)現(xiàn)用戶操作日志功能
這篇文章主要介紹了使用Spring?AOP實(shí)現(xiàn)了用戶操作日志功能,功能實(shí)現(xiàn)需要一張記錄日志的log表,結(jié)合示例代碼給大家講解的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-05-05idea中創(chuàng)建多module的maven工程的方法
這篇文章主要介紹了idea中創(chuàng)建多module的maven工程的方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-10-10