Java - Java集合中的安全失败Fail Safe机制 (CopyOnWriteArrayList)
文章目錄
- Pre
- 概述
- fail-safe的容器—CopyOnWriteArrayList
- add
- remove函數
- 例子
- 缺陷
- 使用場景
Pre
Java - Java集合中的快速失敗Fail Fast 機制
概述
ArrayList使用fail-fast機制自然是因為它增強了數據的安全性。
但在某些場景,我們可能想避免fail-fast機制拋出的異常,這時我們就要將ArrayList替換為使用fail-safe機制的CopyOnWriteArrayList.
采用安全失敗機制的集合容器,在 Iterator 的實現上沒有設計拋出 ConcurrentModificationException 的代碼段,從而避免了fail-fast。
fail-safe的容器—CopyOnWriteArrayList
寫時復制: 當我們往一個容器添加元素的時候,先將當前容器復制出一個新的容器,然后新的容器里添加元素,添加完元素之后,再將原容器的引用指向新的容器。
好處是我們可以對CopyOnWrite容器進行并發的讀,而不需要加鎖,因為當前容器不會添加任何元素。所以CopyOnWrite容器也是一種讀寫分離的思想,讀和寫不同的容器。
add
public boolean add(E e) {// 可重入鎖final ReentrantLock lock = this.lock;// 獲取鎖lock.lock();try {// 元素數組Object[] elements = getArray();// 數組長度int len = elements.length;// 復制數組Object[] newElements = Arrays.copyOf(elements, len + 1);// 存放元素enewElements[len] = e;// 設置數組setArray(newElements);return true;} finally {// 釋放鎖lock.unlock();} }此函數用于將指定元素添加到此列表的尾部,處理流程如下
- 獲取鎖(保證多線程的安全訪問),獲取當前的Object數組,獲取Object數組的長度為length,進入步驟②。
- 根據Object數組復制一個長度為length+1的Object數組為newElements(此時,newElements[length]為null),進入下一步驟。
- 將下標為length的數組元素newElements[length]設置為元素e,再設置當前Object[]為newElements,釋放鎖,返回。這樣就完成了元素的添加。
remove函數
public E remove(int index) {// 可重入鎖final ReentrantLock lock = this.lock;// 獲取鎖lock.lock();try {// 獲取數組Object[] elements = getArray();// 數組長度int len = elements.length;// 獲取舊值E oldValue = get(elements, index);// 需要移動的元素個數int numMoved = len - index - 1;if (numMoved == 0) // 移動個數為0// 復制后設置數組setArray(Arrays.copyOf(elements, len - 1));else { // 移動個數不為0// 新生數組Object[] newElements = new Object[len - 1];// 復制index索引之前的元素System.arraycopy(elements, 0, newElements, 0, index);// 復制index索引之后的元素System.arraycopy(elements, index + 1, newElements, index,numMoved);// 設置索引setArray(newElements);}// 返回舊值return oldValue;} finally {// 釋放鎖lock.unlock();} }-
①獲取鎖,獲取數組elements,數組長度為length,獲取索引的值elements[index],計算需要移動的元素個數(length - index - 1),若個數為0,則表示移除的是數組的最后一個元素,復制elements數組,復制長度為length-1,然后設置數組,進入步驟③;否則,進入步驟②
-
② 先復制index索引前的元素,再復制index索引后的元素,然后設置數組。
-
③ 釋放鎖,返回舊值
例子
import java.util.Iterator; import java.util.concurrent.CopyOnWriteArrayList;class PutThread extends Thread {private CopyOnWriteArrayList<Integer> cowal;public PutThread(CopyOnWriteArrayList<Integer> cowal) {this.cowal = cowal;}public void run() {try {for (int i = 100; i < 110; i++) {cowal.add(i);Thread.sleep(50);}} catch (InterruptedException e) {e.printStackTrace();}} }public class CopyOnWriteArrayListDemo {public static void main(String[] args) {CopyOnWriteArrayList<Integer> cowal = new CopyOnWriteArrayList<Integer>();for (int i = 0; i < 10; i++) {cowal.add(i);}PutThread p1 = new PutThread(cowal);p1.start();Iterator<Integer> iterator = cowal.iterator();while (iterator.hasNext()) {System.out.print(iterator.next() + " ");}System.out.println();try {Thread.sleep(200);} catch (InterruptedException e) {e.printStackTrace();}iterator = cowal.iterator();while (iterator.hasNext()) {System.out.print(iterator.next() + " ");}} }有一個PutThread線程會每隔50ms就向CopyOnWriteArrayList中添加一個元素,并且兩次使用了迭代器,迭代器輸出的內容都是生成迭代器時,CopyOnWriteArrayList的Object數組的快照的內容,在迭代的過程中,往CopyOnWriteArrayList中添加元素也不會拋出異常。
0 1 2 3 4 5 6 7 8 9 100 0 1 2 3 4 5 6 7 8 9 100 101 102 103缺陷
由于寫操作的時候,需要拷貝數組,會消耗內存,如果原數組的內容比較多的情況下,可能導致young gc或者full gc
不能用于實時讀的場景,像拷貝數組、新增元素都需要時間,所以調用一個set操作后,讀取到數據可能還是舊的,雖然CopyOnWriteArrayList 能做到最終一致性,但是還是沒法滿足實時性要求;
使用場景
合適讀多寫少的場景,不過這類慎用
誰也沒法保證CopyOnWriteArrayList 到底要放置多少數據,萬一數據稍微有點多,每次add/set都要重新復制數組,這個代價實在太高 ,容易引起故障
總結
以上是生活随笔為你收集整理的Java - Java集合中的安全失败Fail Safe机制 (CopyOnWriteArrayList)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java - Java集合中的快速失败F
- 下一篇: 每日一博 - Semaphore使用场景