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...