日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學(xué)無(wú)先后,達(dá)者為師

網(wǎng)站首頁(yè) 編程語(yǔ)言 正文

批量導(dǎo)入模板數(shù)據(jù)的時(shí)候遇到的一些關(guān)于多線程的問(wèn)題

作者:_武漢彭于晏_ 更新時(shí)間: 2022-05-11 編程語(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;
  1. 現(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)我放在附件里面

  1. 現(xiàn)在因?yàn)橐粋€(gè)模板導(dǎo)入有萬(wàn)條數(shù)據(jù),如果一條一條處理很浪費(fèi)時(shí)間,這里使用多線程來(lái)分批次處理,多線程真的很多坑,先分批次切片:
    在這里插入圖片描述
  2. 下面是主要的分析環(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

欄目分類(lèi)
最近更新