java.util.ConcurrentModificationException異常原因和解決方法

問題: 

在對集合迭代的時候,如果同時對其進行修改就會拋出java.util.ConcurrentModificationException異常,問題重現:

  
 

原因分析:

進入報錯信息,定位LinkedHashMap719行,modCount != expectedModCount 拋異常。
modCount是HashMap類中的一個成員變量,表示對Map的修改次數,每次調用put()方法或者remove()方法就會對modCount進行加1操作;

expectedModCount:表示對Map修改次數的期望值,它的初始值爲modCount。

 

對圖一中的processes進行遍歷時,modCount == expectedModCount;

1. 循環開始,由於modCount和expectedModCount相等,第一次循環獲取了對象Entry
 
 
 
 
2. 進入循環體後,processes.remove(pid)方法實際上調用了父類HashMap的removeNode方法,最後++modCount,而expectedModCount不變
 
 
3. 繼續循環,在nextNode()方法中,由於modCount != expectedModCount,報ConcurrentModificationException異常!
 

解決方案:

單線程:使用迭代器,通過迭代器進行刪除。
爲什麼使用迭代器刪除不會報錯?
因爲迭代器用了自己封裝的remove方法,最後一步多了一個操作 expectedModCount = modCount
 
多線程:ConcurrentHasMap。
 

CopyOnWriteArrayList 介紹

(1)獨佔鎖效率低:採用讀寫分離思想解決

既然獨佔鎖的效率低下,那我們可以換一種方式,採用讀寫分離式的思想將讀操作和寫操作進行分開即可。

讀操作不加鎖,所有線程都不會阻塞。寫操作加鎖,線程會阻塞。

(2)寫線程獲取到鎖,其他線程包括讀線程阻塞

但是這時候又出現了另外一個問題了:寫線程獲取到鎖之後,其他的讀線程會陷入阻塞。

(3)複製思想:解決問題2

這咋辦呢?我們可以再轉化一下思想:

當我們往一個容器添加元素的時候,不直接往當前容器添加,而是先將當前容器進行 Copy,複製出一個新的容器,然後新的容器裏添加元素,添加完元素之後,再將原容器的引用指向新的容器。

這時候會拋出來一個新的問題,也就是數據不一致的問題。如果寫線程還沒來得及寫會內存,其他的線程就會讀到了髒數據。

這就是CopyOnWriteArrayList 的思想和原理。就是拷貝一份寫。所以使用條件也很侷限,那就是在讀多寫少的情況下比較好。

源碼分析(基於JDK1.8)

既然CopyOnWriteArrayList 主要是針對的讀寫操作,我們可以看看這兩個方法的源碼是如何實現的。

1、get方法

 

整個讀的過程沒有添加任何鎖,就是普通的數組獲取。

2、add方法

寫操作添加了一個鎖ReentrantLock,這個鎖我們可以決定在什麼時候加鎖和釋放更加靈活。保證了寫操作線程安全。能夠體現Copy的思想代碼是 Arrays.copyOf(elements, len + 1);也就是數組複製了一份,最後setArray回去。

CopyOnWriteArrayList 總結

這個容器很簡單,雖然是採用了讀寫分離的思想,但是卻有很大不同,不同之處在於copy。

1、讀寫鎖

讀線程具有實時性,寫線程會阻塞。解決了數據不一致的問題。但是讀寫鎖依然會出現讀線程阻塞等待的情況

2、CopyOnWriteArrayList

讀線程具有實時性,寫線程會阻塞。不能解決數據不一致的問題。但是CopyOnWriteArrayList 不會出現讀線程阻塞等待的情況

 
List和Map等集合類都存在上述問題,請大家在編碼時謹慎使用。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章