背景:最近在做多線程方面的工作,工作中發現多線程中很多坑,這里就有一個List添加對象的誤區,這里做個分享跟大家講講這個坑是怎么形成的怎么樣避免。
示例:
代碼及錯誤:
如果單單只從程序邏輯上看,應該沒有邏輯錯誤,但是結果卻是是有為空值的情況,這時候有些多線程經驗的讀者可能會想到,構造函數也是一個函數,有可能在往List中添加對象的時候,構造函數還沒有將對象返回就執行了添加操作,造成了這個問題的出現,下面我們來驗證一下這個觀點是否正確。
從圖中可以看到,在對象new之后立即執行了取屬性操作,如果構造函數沒有返回立即執行后面肯定會出現空指針異常,但是這里並沒有出問題,說明不是構造函數返回結果的問題,同時也說明了構造函數是具有線程安全的。
為此可以懷疑這個問題是優於List.Add方法造成的,為此,查看一下List.Add方法的源碼可以了解其中的原委。
點開this.EnsureCapacity(this.size+1);方法,如下圖所示:
這時候,我們就可以猜測到問題就出現在這個容量擴展方法這里了,於是我嘗試着修改List的最初容量,使之不需要進行容量擴展,此時程序運行正常,說明問題的確就在這里。
雖然說問題解決了,問題的原因也知道了,但是為什么會有這樣的問題呢?內存擴容是如何形成這個錯誤的呢,於是和六爺討論了一下這個問題,很感謝六爺指點迷津,讓我知道了這其中的具體原因,我畫個簡圖給大家講解一下,希望大家能看明白:
驗證:
假設真的是這個原因造成的應該出現空值的位置應該都是2的整數次冪之后的值,並且如果內存越大拷貝的時間越長,出現空值的幾率就越大,於是改造程序,可以驗證六爺的這個猜測:
代碼:
結果,8192=4096*2,16384=8192*2:
解決方案:
1.擴容List的初始容量為集合需要的實際容量或更大
2.給List.Add方法加鎖
3.使用List的線程安全版本,如下圖所示:
源代碼: