併發容器之CopyOnWriteArrayList

2022-05-12 17:44:16 字數 3602 閱讀 5527

copyonwritearraylist是乙個執行緒安全的arraylist,它的寫操作都是在底層的乙個複製陣列(快照)中進行的,也就是使用了寫時複製策略

什麼是寫時複製策略?

通俗易用地講,不同程序訪問同一資源的時候,只有在寫操作,才會複製乙份新的資料,否則都是訪問同一資源。

每個copyonwritearraylist物件中,都有乙個array物件來存放具體的元素,reentrantlock獨佔鎖物件用來保證同一時刻只有乙個執行緒對array進行修改。

2.1 建構函式

無參建構函式,內部建立了乙個大小為0的object陣列,作為array的初始值。

public copyonwritearraylist()
再來看有參建構函式。

/**

* 入參為集合,將集合裡面的元素複製到新的陣列,把array的引用指向新的陣列

*/public copyonwritearraylist(collection<? extends e> c)

setarray(elements);

}/**

* 入參為陣列

*/public copyonwritearraylist(e tocopyin)

2.2 新增元素
public boolean add(e e)  finally 

}

2.3 獲取指定的元素位置
public e get(int index) 

final object getarray()

private e get(object a, int index)

當某個執行緒呼叫get方法獲取指定位置的元素時,有兩步操作:

1.會首先獲取array

2.再通過下標訪問指定位置的元素

在這個過程中並沒有加鎖同步。可以假設有這個場景,有x、y兩個執行緒,x執行get方法,當x在執行完步驟1後步驟2前。

此時,y執行緒進來了,y執行緒要進行remove操作,當然就是獲取鎖進行寫時複製,複製乙份新的陣列,刪除此陣列後,把array指向新陣列。

但是,這個時候x執行緒讀取到的還是原來舊的陣列,這種情況,就是寫時複製策略產生的弱一致性問題。

2.4 修改指定的元素

public e set(int index, e element)  else 

return oldvalue;

} finally

}

跟上面的方法一樣,也是先拿到獨佔鎖,呼叫get方法獲取指定元素的位置,如果得到的值與新值不一樣,則複製乙個新陣列,把元素設定到新陣列中,並讓array指向新陣列。

如果新值等於舊值,為了保證volatile語義,還是需要重新設定array,雖然array的內容沒有改變。

2.5 刪除元素

public e remove(int index) 

return oldvalue;

} finally

}

沒什麼特別的,首先拿到要刪除元素的值,判斷是否是刪除最後乙個元素,是的話,則直接拷貝0~len-1的陣列,否則,新建乙個長度為len-1的陣列,分兩次複製刪除後剩餘的陣列。

2.6 弱一致性的迭代器

public iteratoriterator() 

static final class cowiteratorimplements listiterator

public boolean hasnext()

public boolean hasprevious()

@suppresswarnings("unchecked")

public e next()

@suppresswarnings("unchecked")

public e previous()

public int nextindex()

public int previousindex()

/*** not supported. always throws unsupportedoperationexception.

* @throws unsupportedoperationexception always;

* is not supported by this iterator.

*/public void remove()

/*** not supported. always throws unsupportedoperationexception.

* @throws unsupportedoperationexception always;

* is not supported by this iterator.

*/public void set(e e)

/*** not supported. always throws unsupportedoperationexception.

* @throws unsupportedoperationexception always;

* is not supported by this iterator.

*/public void add(e e)

@override

public void foreachremaining(consumer<? super e> action)

cursor = size;

}}

當呼叫iterator()方法獲取迭代器的時候,實際上會返回乙個cowiterator物件,cowiterator物件的snapshot變數儲存了當前list的內容,cursor是遍歷list時資料的下標。

這裡我們看到明明snapshot是引用,而不是快照。是因為如果在遍歷的期間,其他執行緒對該list進行了寫操作,那麼snapshot實際上就是快照,因為寫操作完成後,list裡面就是新的陣列,但是此時老陣列還是被snapshot引用。

其他執行緒對該list的寫操作是不可見的,因為他們操作的是不同陣列,這就是弱一致性。

可以通過以下的乙個例子去驗證

public class copytest );

// 啟動執行緒前,先去獲取該list的迭代器

iteratoriterator = list.iterator();

t.start();

// 等待t執行完畢

t.join();

while (iterator.hasnext())

}}

輸出:12

345子執行緒t進行的操作,從輸出的結果來看,乙個都沒有生效,因為父執行緒獲取的是舊的陣列。

copyonwritearraylist使用寫時複製策略來保證list的一致性,而獲取獲取->修改->寫入這三步操作並不是原子性,所以在寫操作的時候使用了獨佔鎖,來保證某段時間只有乙個執行緒來操作list。另外,copyonwritearraylist提供了弱一致性的迭代器,保證獲取迭代器後,其他執行緒對list的修改是不可見的,迭代器遍歷的陣列是乙個快照。

高併發之同步容器

一 採用傳統的方法 經典面試題 寫乙個固定容量的容器,擁有put和get方法,以及getcount方法 能夠支援2個生產者執行緒以及10個消費者執行緒的阻塞呼叫 點 生產者消費者模式 如果呼叫 get方法時,容器為空,get方法就需要阻塞等待 如果呼叫 put方法時,容器滿了,put方法就需要阻塞等...

併發基礎 11 併發 容器

要實現乙個執行緒安全的佇列有兩個方式 一種是使用阻塞演算法,另一種是使用非阻塞演算法。阻塞演算法 使用阻塞演算法的佇列可以用乙個鎖 入隊和出隊同一把鎖 或兩把鎖 入隊和出隊用不同的鎖 來實現。非阻塞的實現方式則可以使用迴圈cas的方式來實現。concurrentlinkedqueue非阻塞執行緒安全...

併發程式設計9 併發容器

解決併發情況下的容器執行緒安全問題 譬如 vector,hashtable,都是給予同步鎖實現的 concurrent包下的類,大部分都是使用系統底層的實現,類似於native public class test09 latch.countdown for thread t arr try catc...