轉載自:
線程安全的CopyOnWriteArrayList介紹 證明CopyOnWriteArrayList是線程安全的先寫一段代碼證明CopyOnWriteArrayList确實是線程安全的。
讀線程
寫線程
測試代碼
運作上面的代碼,沒有報出
java.util.ConcurrentModificationException
1
說明了CopyOnWriteArrayList并發多線程的環境下,仍然能很好的工作。
CopyOnWriteArrayList如何做到線程安全的CopyOnWriteArrayList使用了一種叫
寫時複制的方法,當有新元素添加到CopyOnWriteArrayList時,先從原有的數組中拷貝一份出來,然後在新的數組做寫操作,寫完之後,再将原來的數組引用指向到新數組。
當有新元素加入的時候,如下圖,建立新數組,并往新數組中加入一個新元素,這個時候,array這個引用仍然是指向原數組的。
當元素在新數組添加成功後,将array這個引用指向新數組。
CopyOnWriteArrayList的整個add操作都是在
鎖的保護下進行的。
這樣做是為了避免在多線程并發add的時候,
複制出多個副本出來,把資料搞亂了,導緻最終的數組資料不是我們期望的。
CopyOnWriteArrayList的add操作的源代碼如下:
publicbooleanadd(E e) {//1、先加鎖final ReentrantLocklock=this.lock;lock.lock();try{ Object[] elements = getArray();intlen = elements.length;//2、拷貝數組Object[] newElements = Arrays.copyOf(elements, len +1);//3、将元素加入到新數組中newElements[len] = e;//4、将array引用指向到新數組setArray(newElements);returntrue; }finally{//5、解鎖lock.unlock(); }}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
由于所有的寫操作都是在新數組進行的,這個時候如果有線程并發的寫,則通過鎖來控制,如果有線程并發的讀,則分幾種情況:
1、如果寫操作未完成,那麼直接讀取原數組的資料;
2、如果寫操作完成,但是引用還未指向新數組,那麼也是讀取原數組資料;
3、如果寫操作完成,并且引用已經指向了新的數組,那麼直接從新數組中讀取資料。
可見,CopyOnWriteArrayList的
讀操作是可以不用
加鎖的。
CopyOnWriteArrayList的使用場景通過上面的分析,CopyOnWriteArrayList 有幾個缺點:
1、由于寫操作的時候,需要拷貝數組,會消耗記憶體,如果原數組的内容比較多的情況下,可能導緻young gc或者full gc
2、不能用于
實時讀的場景,像拷貝數組、新增元素都需要時間,是以調用一個set操作後,讀取到資料可能還是舊的,雖然CopyOnWriteArrayList 能做到
最終一緻性,但是還是沒法滿足實時性要求;
CopyOnWriteArrayList 合适
讀多寫少的場景,不過這類慎用
因為誰也沒法保證CopyOnWriteArrayList 到底要放置多少資料,萬一資料稍微有點多,每次add/set都要重新複制數組,這個代價實在太高昂了。在高性能的網際網路應用中,這種操作分分鐘引起故障。
CopyOnWriteArrayList透露的思想如上面的分析CopyOnWriteArrayList表達的一些思想:
1、讀寫分離,讀和寫分開
2、最終一緻性
3、使用另外開辟空間的思路,來解決并發沖突
參考的文章 JAVA中的COPYONWRITE容器