多線程主線程與子線程執(zhí)行順序問題
案發(fā)現(xiàn)場
上述代碼目標(biāo)是完成在庫600w微信公眾號會員數(shù)據(jù)的清洗媒峡,通過jdbc游標(biāo)一次性從從庫中拿出所有數(shù)據(jù),游標(biāo)while循環(huán)遍歷組裝一個100位的數(shù)組证杭,并通過多線程去拿著這個數(shù)組完成后續(xù)操作徽职;
期望如下圖:
但是VΦ蕖!實際結(jié)果
在線程池執(zhí)行數(shù)組拼裝的時候熔吗,每一個線程執(zhí)行的都是當(dāng)前第一個100頁的數(shù)組(ps:后來得知真實情況是線程并不是每次都是執(zhí)行第一次辆床,而是看運氣可能會執(zhí)行前面線程執(zhí)行過的數(shù)組,純看cpu資源搶占的運氣)
因為清洗數(shù)據(jù)都是大半夜開始執(zhí)行桅狠,當(dāng)跑到大半夜的時候發(fā)現(xiàn)問題讼载,立即停掉服務(wù)定位問題;中間一直走過許多彎路垂攘,首先考慮的是數(shù)據(jù)結(jié)構(gòu)為非線程安全维雇,花了大量力氣把a(bǔ)rrayBuffer換成其他安全數(shù)據(jù)結(jié)構(gòu),但是依然無果晒他,各種無果情況下吱型,特在此特別鳴謝 遠(yuǎn)在深圳前領(lǐng)導(dǎo)老王-祥哥,在他指導(dǎo)下陨仅,迅速定位問題津滞,照搬代碼(照搬過程中其實也是不知其所以然,只為解決問題灼伤。触徐。。狐赡。)解決當(dāng)下問題撞鹉;
附上修改之后代碼如下:
上述代碼運行如下:
可以看到經(jīng)過修改之后代碼正確按照期望運行,每次游標(biāo)組裝100個arrayBuffer之后開啟多線程異步去處理颖侄,主線程繼續(xù)遍歷下一頁的arrayBuffer鸟雏,且每個線程數(shù)據(jù)不會亂
相比較兩者差異可以發(fā)現(xiàn),唯一經(jīng)過改動的代碼為
if (arrayBuffer.length == 99) {
val asList = arrayBuffer.toList
exec.execute(()=>{
logger.info(s"子線程開始請求${asList}")
})
logger.info(s"主線程開始${arrayBuffer}")
arrayBuffer.clear()
}
每次arraybuffer.toList()方法放到了線程池之外的主線程去執(zhí)行览祖,子線程則每次執(zhí)行最新的arrayBuffer
問題原因:線程池多線程執(zhí)行過程中主線程與子線程執(zhí)行順序問題:
在線程池開啟時孝鹊,其實背后做的是mainThread中從線程池拿到線程資源開啟子線程異步 去執(zhí)行線程池excute()函數(shù)方法,因為在多線程處理情況過程中展蒂,主線程因為已經(jīng)開始執(zhí)行又活,當(dāng)前是霸占了cpu資源的苔咪,而線程池中線程為mainThread線程中的子線程,子線程的執(zhí)行順序是低于main主線程的柳骄,所以當(dāng)主線程拿到cpu資源之后团赏,子線程會去拿cpu資源時,是低于主線程的耐薯,所以導(dǎo)致arrayBuffer.toList是子線程去操作的馆里,也就是都會優(yōu)先去執(zhí)行主線程中的 arrayBuffer.clear();這樣子線程中永遠(yuǎn)在跟主線程搶占資源的時候偶爾會出現(xiàn)上述代碼中執(zhí)行前面一條線程的arrayBuffer;
剛開始時可柿,只有主線程在使用CPU的執(zhí)行權(quán),因為其他兩個線程還沒有被創(chuàng)建丙者,這時主線程的代碼就自上而下的去執(zhí)行复斥。
當(dāng)主線程的內(nèi)容執(zhí)行完畢后,就開始創(chuàng)建并啟動其他的線程械媒,此時目锭,棧中有三個線程:主線程、Thread-0和Thread-1線程(上述demo中只開啟了兩個)纷捞。但是主線程中的arrayBuffer最后toList()是在子線程去執(zhí)行的痢虹,所以現(xiàn)在相當(dāng)于只有Thread-0和Thread-1線程搶資源情況都會去執(zhí)行主線程中arrayBuffer1,而不是我們要求的t1 =>arraybuffer1主儡,t2 =>arraybuffer2奖唯,因此我們會看到這兩個線程在輪流搶占CPU的執(zhí)行權(quán)且都在執(zhí)行arraybuffer1
基于上述原理,所以我們要做的核心目的是讓子線程每次執(zhí)行的都是最新的arrayBuffer糜值,
val asList = arrayBuffer.toList
所以老司機(jī)的建議做法吧arraybuffer的toList()從子線程參數(shù)放到main主線程中(因為arrayBuffer.clear在主線程中執(zhí)行丰捷,他是優(yōu)先執(zhí)行的)