Java並發編程之-list集合的並發.
我們都知道Java集合類中的arrayList是線程不安全的。那么怎么證明是線程不安全的呢?怎么解決在並發環境下使用安全的list集合類呢?
本篇是《凱哥(凱哥Java:kagejava)並發編程學習》系列之《並發集合系列》教程的第一篇:
本文主要內容:怎么證明arrayList不是線程安全的?怎么解決這個問題?以及遇到問題解決的四個步驟及從源碼來分析作者思路。
一:怎么證明arrayList在並發情況下是線程不安全的呢?
創建一個list,用多個線程向list中添加數據。來看看結果
查看運行結果:
我們發現了一個異常:java.util.ConcurrentModificationException
java.util.ConcurrentModificationException是什么
這個異常什么意思呢?我們來看看這個異常源碼中類的注釋信息:
This exception may be thrown by methods that have detected concurrent(此異常可能由檢測到並發的方法引發).
一般可以理解為,這是並發導致的異常。那么在並發情況下出現了異常。是不是從側面說明arrayList是不安全的呢?
二:怎么解決這個問題
這里凱哥順便說下,解決問題的一般步驟。
1:怎么操作導致的故障及現象是什么?
操作:多個線程對list進行add添加操作的時候
結果:拋出了java.util.ConcurrentModificationException異常信息
2:分析產生這個問題的原因
舉個現實生活中的例子。簽到表,這個大家都見過吧,應該都簽到過吧。比如現在有個會議很多人來參與,需要簽到。現在,司小司正在簽到表上寫自己的名字時候,小明非要看簽到表上面有沒有自己名字。因為司小司正在簽到進行中,小明硬是要查看,把簽到表搶過去,結果就是簽到表被撕壞了或者是司小司的筆在簽到表上留下了長長的痕跡。如果上面這個例子用計算機角度分析的話。
兩個線程(司小司和小明)對一個共享變量(簽到表,可以理解為是人名的集合)進行讀寫操作(司小司簽到是寫操作,小明要查看自己是否簽到了,可以理解為讀操作),因為兩個線程都來競爭共享資源。后果就是簽到表被撕壞了或者是司小司的筆在簽到表上留下了長長的痕跡。異常現象。用到上面我們多個線程對list進行操作的時候,就拋異常了多線程並發修改異常信息。
3:解決方案是什么?
1:使用線程安全的List的子類Vectory
List list = new Vectory();
查看vectory的add方法源碼:
發現,原來vector的add方法是加的並發鎖來保證線程安全的
2:使用collections工具類的sync方法
List list = Colletcions.synchronizedList(new ArrayList<>());
查看源碼:
原來都是synchronized的。
我們在來看看synchronizedList方法上面的注釋。
發現,原來源碼中是把整個list對象作為同步鎖的鎖。這樣來保證線程安全的
4:解決方案可以優化嗎?優化的建議是什么?
我們知道synchronized關鍵字是同步鎖機制。強制並行轉化成串行的一種方案。這種對性能消耗比較大。有沒有更其他可以優化的方案嗎?
來看看使用JUC並發包下的:CopyOnWriteArrayList(寫時復制list)來解決吧。
先來看看這個類的add方法的源碼:
從源碼中,我們可以看到復制了一個新的list集合,將新元素在新集合中操作。那么為什么這種操作就不會出現並發異常呢?
因為這種思想,可以理解為讀寫分離的思想。因為get還是使用原來list的get的方法。寫的時候,在復制一份原來的,然后再復制出來的基礎上進行修改的。那么怎么保證數據問題呢?我們從源碼中可以看到使用到了ReentrantLock(關於鎖相關的。凱哥(凱哥Java:kaigejava)將在后面詳細的講解的)鎖來控制的。
那么現在使用CopyOnWriteArrayList來模擬下文章開頭簽到例子。
司小司再簽到的時候,先把簽到表復制一份,然后再新的復制出來的簽到表中進行簽到。小明是原來簽到表查看自己的信息的。這樣就不會出現爭強情況了。