事情經(jīng)過
由于公司需要進(jìn)行公眾號遷移忙芒,需要對線上的openId進(jìn)行清洗,由于數(shù)據(jù)量巨大供屉,并且依賴了微信的外部接口行冰,所以決定用多線程進(jìn)行處理。
代碼如下:
val exec = {
new ThreadPoolExecutor(20, 20, 0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue[Runnable](100),
new ThreadPoolExecutor.CallerRunsPolicy()
)
}
MemberDataSource.mysqlData.withConnection(conn => {
val stmt = conn.createStatement(java.sql.ResultSet.TYPE_FORWARD_ONLY, java.sql.ResultSet.CONCUR_READ_ONLY)
stmt.setFetchSize(Integer.MIN_VALUE)
//and id>2107380
val rs = stmt.executeQuery("select * from test where status=0 ")
val arrayBuffer = new ArrayBuffer[String](100)
while (rs.next()) {
val row = ResultSetMapper.material[MemberChannelList].from(rs)
arrayBuffer += row.openId
if (arrayBuffer.length == 100) {
exec.execute(()=> logger.info(s"${arrayBuffer.toList}"))
arrayBuffer.clear
}
}
})
主要思路為:模仿去年切非瑪時(shí)的方法伶丐,使用數(shù)據(jù)庫游標(biāo)將數(shù)據(jù)查出悼做,封裝一個(gè)長度為100的數(shù)組,然后使用多線程對微信進(jìn)行請求獲取結(jié)果撵割。
但是在測試的時(shí)候發(fā)現(xiàn)贿堰,每個(gè)線程請求的數(shù)據(jù)發(fā)生了錯(cuò)亂,每個(gè)線程請求的數(shù)據(jù)有重復(fù)啡彬,這樣造成了數(shù)據(jù)的重復(fù)操作,于是立刻停止故硅。
例如:線程11和線程12請求的數(shù)據(jù)有相同的庶灿,于是懷疑是多線程導(dǎo)致的線程錯(cuò)亂的問題。
于是將問題原因鎖定在arrayBuffer上吃衅,認(rèn)為它不是線程安全的往踢,所以導(dǎo)致了線程請求的數(shù)據(jù)出現(xiàn)重復(fù),于是決定改成ConcurrentLinkList徘层,但是依然請求的結(jié)果如此峻呕,折騰了一晚上,不管怎么修改趣效,依然線程之間有重復(fù)數(shù)據(jù)瘦癌,跟孫政兩人修改代碼到天明,依然沒有找到問題跷敬,兩個(gè)人都準(zhǔn)備放棄了讯私,決定就單線程的跑完算了,慢點(diǎn)就慢點(diǎn)西傀,在早上準(zhǔn)備放棄的時(shí)候斤寇,抱著最后一絲希望,請教遠(yuǎn)在深圳的祥哥拥褂,祥哥快速定位問題娘锁,并且寫出了正確的代碼
if(arrayBuffer.length == 99) {
val asList = arrayBuffer.toList
exec.execute ( openIdInsertMethod(asList) )
arrayBuffer.clear
}
一開始拿到代碼,因?yàn)橐呀?jīng)一晚上沒睡饺鹃,還沒明白到底跟自己的有什么不同莫秆,只是盲目的粘貼上去间雀,但是神奇的發(fā)現(xiàn)問題解決了,請求的參數(shù)里面再?zèng)]出現(xiàn)之前的線程錯(cuò)亂問題
當(dāng)時(shí)感覺特別不可思議馏锡,立刻比對之前的代碼
發(fā)現(xiàn)代碼唯一的不同在于
修改前:
exec.execute ( openIdInsertMethod(arrayBuffer.toList) )
修改后:
val asList = arrayBuffer.toList
exec.execute ( openIdInsertMethod(asList) )
一個(gè)是在線程池里面toList雷蹂,一個(gè)是在外面定義一個(gè)變量去toList
終于明白原因:
在一個(gè)線程中開啟另外一個(gè)新線程,則新開線程稱為該線程的子線程杯道,子線程初始優(yōu)先級與父線程相同匪煌。不過主線程先啟動(dòng)占用了cpu資源,因此主線程總是優(yōu)于子線程党巾。然而萎庭,其實(shí)設(shè)置了優(yōu)先級,也無法保障線程的執(zhí)行次序齿拂。只不過驳规,優(yōu)先級高的線程獲取CPU資源的概率較大,優(yōu)先級低的并非沒機(jī)會(huì)執(zhí)行署海。
所以arrayBuffer.clear有可能先執(zhí)行吗购,那么exec.execute執(zhí)行的arrayBuffer就是一個(gè)空的數(shù)組,所以就出現(xiàn)了多個(gè)線程出現(xiàn)了重復(fù)數(shù)據(jù)的原因砸狞,所以我們要保證的是exec.execute每次執(zhí)行完arrayBuffer后再進(jìn)行clear即可捻勉。而不是一開始定位的保證arrayBuffer的安全性。所以將toList操作放在外面去執(zhí)行后刀森,多線程數(shù)據(jù)就正常了踱启。
在此感謝遠(yuǎn)在深圳的祥哥,立刻定位到問題研底,不然我們這次數(shù)據(jù)清洗可能真的要跑30多個(gè)小時(shí)埠偿。 再次感謝??????