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