網(wǎng)站首頁(yè) 編程語(yǔ)言 正文
項(xiàng)目場(chǎng)景:
在項(xiàng)目批量導(dǎo)入模板數(shù)據(jù)的時(shí)候遇到的一些關(guān)于多線程的問(wèn)題
問(wèn)題描述
眾所周知在批量導(dǎo)入excel的數(shù)據(jù)時(shí)一般使用阿里巴巴的EasyExcel,內(nèi)置多線程的處理,但是有一個(gè)很大的弊端就是,baseExcel的每一個(gè)屬性都是固定的,才可以使用,現(xiàn)在就是導(dǎo)入的模板根據(jù)數(shù)據(jù)庫(kù)的字段實(shí)時(shí)變化,對(duì)于easyExcel并不適用(也可能是我對(duì)easyExcel使用并不熟練),在網(wǎng)上未找到解決辦法之后,只能自己按照easyExcel的邏輯實(shí)現(xiàn)功能,下面是我在過(guò)程中遇到的一系列問(wèn)題,主要是關(guān)于多線程和數(shù)據(jù)庫(kù)連接的問(wèn)題:
##正常使用esayExcel模板:
@ExcelPorperty
private String name;
@ExcelPorperty
private String code;
- 現(xiàn)在導(dǎo)入模板隨數(shù)據(jù)庫(kù)的數(shù)據(jù)而變化,固不能使用easyExcel導(dǎo)入,下面是我的解決辦法:通過(guò)ExcelUtil來(lái)parse每一行數(shù)據(jù),和數(shù)據(jù)庫(kù)讀取出來(lái)的column的map做對(duì)照,然后手動(dòng)處理每一行的數(shù)據(jù)(這里還是比較簡(jiǎn)單的):
List<Map<String,Object>> mapList = ImportExcelUtil.parseExcel(file.getInputStream(),map);
map是數(shù)據(jù)庫(kù)查詢出來(lái)的column,file是導(dǎo)入的文件,工具類(lèi)我放在附件里面
- 現(xiàn)在因?yàn)橐粋€(gè)模板導(dǎo)入有萬(wàn)條數(shù)據(jù),如果一條一條處理很浪費(fèi)時(shí)間,這里使用多線程來(lái)分批次處理,多線程真的很多坑,先分批次切片:
- 下面是主要的分析環(huán)節(jié),主要是對(duì)數(shù)據(jù)進(jìn)行去重,數(shù)據(jù)庫(kù)查詢字段是否匹配,數(shù)據(jù)庫(kù)是否重復(fù),業(yè)務(wù)邏輯不詳細(xì)講解,這里使用了CountDownLatch(CountDownLatch詳解)同步鎖,CountDownLatch允許一個(gè)或者多個(gè)線程去等待其他線程完成操作。多線程一般和鎖配合使用,這里也是導(dǎo)致我后面遇到了一個(gè)問(wèn)題:
CountDownLatch latch = new CountDownLatch(mapList.size());
主要遇到的問(wèn)題:
1.
多線程共享靜態(tài)變量的問(wèn)題
學(xué)習(xí)過(guò)多線程的應(yīng)該了解靜態(tài)變量,這邊是原本打算寫(xiě)一個(gè)靜態(tài)變量的List<>收集每個(gè)線程發(fā)生的報(bào)錯(cuò),后面統(tǒng)一返回,首先在回答共享變量之前,我們應(yīng)該搞清的是什么是線程安全?
對(duì)于線程的安全,通過(guò)博客我們可能會(huì)得到很多的答案,但在這里我結(jié)合一點(diǎn)自己的想法和感受談?wù)劸€程安全
我想本質(zhì)的問(wèn)題就是:啟動(dòng)線程的方法是start方法,但是真正運(yùn)行線程的方法是run方法,所以線程安全不安全取決于run方法中的代碼的執(zhí)行的結(jié)果是否一致,如果start啟動(dòng)的的是多個(gè)線程,而run中運(yùn)行的是多個(gè)線程,如果當(dāng)單個(gè)線程運(yùn)行的結(jié)果和多個(gè)線程運(yùn)行的結(jié)果不一樣時(shí),那么線程必定是不安全的,反之,如果這多個(gè)線程運(yùn)行的結(jié)果和單個(gè)線程運(yùn)行的結(jié)果是一致的,那么必然是線程安全的,也就是說(shuō),run’中的代碼運(yùn)行的結(jié)果是一致的,所以這個(gè)問(wèn)題就解決了;下面我們回答共享變量的問(wèn)題;
假如有三個(gè)線程:線程一,線程二,線程三,三個(gè)線程同時(shí)啟動(dòng)區(qū)訪問(wèn)一個(gè)共享變量x,從java虛擬機(jī)的角度來(lái)說(shuō),共享變量存在主內(nèi)存中.但是變量的修改不能再主內(nèi)存中修改,因此每一個(gè)變量對(duì)應(yīng)的每一個(gè)線程都有一個(gè)工作內(nèi)存區(qū)域,而這個(gè)區(qū)域就是每一個(gè)變量的修改的區(qū)域,也就是說(shuō):變量的修改是在工作內(nèi)存中修改的,而非是在主內(nèi)存中修改的,為了保證共享變量的可見(jiàn)性,加一個(gè)互斥鎖,這個(gè)目的是保證某一段時(shí)間只能由一個(gè)線程修改變量,但是下一個(gè)線程讀取變量的值,一定是在主內(nèi)存中讀取的,其實(shí)說(shuō)白了就是一下三點(diǎn):
1.主內(nèi)存-------->修改后的變量,用于讀取
2.工作內(nèi)存------>共享變量真正修改的地方
3.互斥鎖---------->保證共享變量的安全性;
volidate:
強(qiáng)迫修改后的值,保存在主內(nèi)存中;
原子性:
sybchonized:具有可見(jiàn)性和原子性,volidate不具有原子性;
但是靜態(tài)變量在所有線程共享,假設(shè)別的租戶同時(shí)使用了這個(gè)方法導(dǎo)入模板,很可能會(huì)導(dǎo)致數(shù)據(jù)發(fā)生互通,所以這個(gè)方案無(wú)法實(shí)現(xiàn),這個(gè)bug也是在上線之后才發(fā)現(xiàn),后面更改成另一種方案:
使用局部變量,每個(gè)線程返回到主線程當(dāng)中,然后統(tǒng)一收集,但是代碼量增多
2.
多線程上下文不共享的問(wèn)題
公司使用上下文來(lái)獲取當(dāng)前用戶的公司和租戶信息,但是上下文只在主線程中有效,在線程池(線程池詳解)中沒(méi)有上下文,解決辦法比較簡(jiǎn)單,手動(dòng)創(chuàng)造上下文,把值傳入其他線程之中
3.
線程池配置問(wèn)題
關(guān)于項(xiàng)目中的線程配置,多數(shù)看項(xiàng)目具體的數(shù)據(jù)量和cpu數(shù)量,這邊項(xiàng)目大概數(shù)據(jù)1w條,每個(gè)線程500條,大概需要20個(gè)線程,線程池的優(yōu)化對(duì)效率的作用至關(guān)重要,在項(xiàng)目中我因?yàn)殚_(kāi)始線程池配置的maxPoolSize設(shè)置的Integer.MAX,導(dǎo)致數(shù)據(jù)內(nèi)存溢出,項(xiàng)目oom,所以線程池的配置至關(guān)重要。ThreadPoolExecutor部分源碼
構(gòu)造方法:
public ThreadPoolExecutor(int corePoolSize, //核心線程數(shù)量
int maximumPoolSize,// 最大線程數(shù)
long keepAliveTime, // 最大空閑時(shí)間
TimeUnit unit, // 時(shí)間單位
BlockingQueue<Runnable> workQueue, // 任務(wù)隊(duì)列
ThreadFactory threadFactory, // 線程工廠
RejectedExecutionHandler handler // 飽和處理機(jī)制
)
4.
CountDownLatch 未釋放導(dǎo)致數(shù)據(jù)庫(kù)連接過(guò)多
CountDownLatch 是一個(gè)適用于當(dāng)前項(xiàng)目的鎖,這邊設(shè)置的size等一線程總數(shù),在每個(gè)線程處理完畢之后執(zhí)行countDownLatch.countDown釋放,主線程中使用countDownLatch.await()阻塞,當(dāng)所有線程執(zhí)行完畢之后countDownLatch才釋放,主線程運(yùn)行,可以有效的處理數(shù)據(jù),但是這也導(dǎo)致了一個(gè)問(wèn)題:
其他線程出現(xiàn)了問(wèn)題:例如數(shù)據(jù)不匹配,數(shù)據(jù)庫(kù)查詢報(bào)錯(cuò),非拋出式異常導(dǎo)致線程中斷,并未釋放countDownLatch,主線程一直countDownLatch.await()導(dǎo)致線程一直阻塞,占用數(shù)據(jù)庫(kù)連接,在多次調(diào)試之后,占用的數(shù)據(jù)路連接已經(jīng)超過(guò)數(shù)據(jù)庫(kù)的最大連接數(shù),最后數(shù)據(jù)庫(kù)連接占用過(guò)多導(dǎo)致出錯(cuò)
這個(gè)bug十分隱晦所以在開(kāi)始的時(shí)候沒(méi)有發(fā)現(xiàn),但是仍然值得重視,解決辦法就是,在線程的try-catch-finally中加入countDownLatch.countDown,無(wú)論線程是否報(bào)錯(cuò)都釋放鎖,主線程不阻塞
總結(jié)
多線程在使用過(guò)程中涉及鎖,靜態(tài)變量,上下文,線程間的同步與互斥,線程池配置,需要一定的積累才能使用的好,在很多官方模塊中也使用了線程,以前總是使用現(xiàn)成的輪子,現(xiàn)在造輪子才知道輪子多難造,還是盡量使用官方的代碼塊,所以easyExcel什么時(shí)候有根據(jù)數(shù)據(jù)庫(kù)實(shí)時(shí)變動(dòng)column的功能,我就不用操這么多心了,只能說(shuō)代碼的迭代需要一代接一代
原文鏈接:https://blog.csdn.net/qq_43171656/article/details/124683092
相關(guān)推薦
- 2022-07-30 go?redis之redigo的使用_Golang
- 2023-01-17 C#實(shí)現(xiàn)自定義ListBox背景的示例詳解_C#教程
- 2022-07-26 面向?qū)ο驩OP基礎(chǔ)理解
- 2023-07-06 mac配置idea自帶的maven3環(huán)境變量
- 2022-08-15 springboot使用配置文件配置bean屬性產(chǎn)生中文亂碼問(wèn)題
- 2023-05-20 openGauss數(shù)據(jù)庫(kù)共享存儲(chǔ)特性概述_數(shù)據(jù)庫(kù)其它
- 2022-02-26 SpringSecurity 自定義JwtAuthorFilter基于JWT的Token驗(yàn)證
- 2023-04-10 pytest?用例執(zhí)行失敗后其他不再執(zhí)行_python
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細(xì)win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運(yùn)行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲(chǔ)小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運(yùn)算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認(rèn)證信息的處理
- Spring Security之認(rèn)證過(guò)濾器
- Spring Security概述快速入門(mén)
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯(cuò)誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實(shí)現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡(jiǎn)單動(dòng)態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對(duì)象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊(duì)列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支