Java concurrency集合之 CopyOnWriteArrayList_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
CopyOnWriteArrayList介紹
它相當(dāng)于線程安全的ArrayList。和ArrayList一樣,它是個(gè)可變數(shù)組;但是和ArrayList不同的時(shí),它具有以下特性:
1. 它最適合于具有以下特征的應(yīng)用程序:List 大小通常保持很小,只讀操作遠(yuǎn)多于可變操作,需要在遍歷期間防止線程間的沖突。
2. 它是線程安全的。
3. 因?yàn)橥ǔP枰獜?fù)制整個(gè)基礎(chǔ)數(shù)組,所以可變操作(add()、set() 和 remove() 等等)的開(kāi)銷(xiāo)很大。
4. 迭代器支持hasNext(), next()等不可變操作,但不支持可變 remove()等操作。
5. 使用迭代器進(jìn)行遍歷的速度很快,并且不會(huì)與其他線程發(fā)生沖突。在構(gòu)造迭代器時(shí),迭代器依賴于不變的數(shù)組快照。
CopyOnWriteArrayList原理和數(shù)據(jù)結(jié)構(gòu)
CopyOnWriteArrayList的數(shù)據(jù)結(jié)構(gòu),如下圖所示:

說(shuō)明:
1. CopyOnWriteArrayList實(shí)現(xiàn)了List接口,因此它是一個(gè)隊(duì)列。
2. CopyOnWriteArrayList包含了成員lock。每一個(gè)CopyOnWriteArrayList都和一個(gè)互斥鎖lock綁定,通過(guò)lock,實(shí)現(xiàn)了對(duì)CopyOnWriteArrayList的互斥訪問(wèn)。
3. CopyOnWriteArrayList包含了成員array數(shù)組,這說(shuō)明CopyOnWriteArrayList本質(zhì)上通過(guò)數(shù)組實(shí)現(xiàn)的。
下面從“動(dòng)態(tài)數(shù)組”和“線程安全”兩個(gè)方面進(jìn)一步對(duì)CopyOnWriteArrayList的原理進(jìn)行說(shuō)明。
1. CopyOnWriteArrayList的“動(dòng)態(tài)數(shù)組”機(jī)制 -- 它內(nèi)部有個(gè)“volatile數(shù)組”(array)來(lái)保持?jǐn)?shù)據(jù)。在“添加/修改/刪除”數(shù)據(jù)時(shí),都會(huì)新建一個(gè)數(shù)組,并將更新后的數(shù)據(jù)拷貝到新建的數(shù)組中,最后再將該數(shù)組賦值給“volatile數(shù)組”。這就是它叫做CopyOnWriteArrayList的原因!CopyOnWriteArrayList就是通過(guò)這種方式實(shí)現(xiàn)的動(dòng)態(tài)數(shù)組;不過(guò)正由于它在“添加/修改/刪除”數(shù)據(jù)時(shí),都會(huì)新建數(shù)組,所以涉及到修改數(shù)據(jù)的操作,CopyOnWriteArrayList效率很
低;但是單單只是進(jìn)行遍歷查找的話,效率比較高。
2. CopyOnWriteArrayList的“線程安全”機(jī)制 -- 是通過(guò)volatile和互斥鎖來(lái)實(shí)現(xiàn)的。(01) CopyOnWriteArrayList是通過(guò)“volatile數(shù)組”來(lái)保存數(shù)據(jù)的。一個(gè)線程讀取volatile數(shù)組時(shí),總能看到其它線程對(duì)該volatile變量最后的寫(xiě)入;就這樣,通過(guò)volatile提供了“讀取到的數(shù)據(jù)總是最新的”這個(gè)機(jī)制的
保證。(02) CopyOnWriteArrayList通過(guò)互斥鎖來(lái)保護(hù)數(shù)據(jù)。在“添加/修改/刪除”數(shù)據(jù)時(shí),會(huì)先“獲取互斥鎖”,再修改完畢之后,先將數(shù)據(jù)更新到“volatile數(shù)組”中,然后再“釋放互斥鎖”;這樣,就達(dá)到了保護(hù)數(shù)據(jù)的目的。
CopyOnWriteArrayList函數(shù)列表
// 創(chuàng)建一個(gè)空列表。 CopyOnWriteArrayList() // 創(chuàng)建一個(gè)按 collection 的迭代器返回元素的順序包含指定 collection 元素的列表。 CopyOnWriteArrayList(Collection<? extends E> c) // CopyOnWriteArrayList(E[] toCopyIn)
創(chuàng)建一個(gè)保存給定數(shù)組的副本的列表。
// 將指定元素添加到此列表的尾部。 boolean add(E e) // 在此列表的指定位置上插入指定元素。 void add(int index, E element) // 按照指定 collection 的迭代器返回元素的順序,將指定 collection 中的所有元素添加此列表的尾部。 boolean addAll(Collection<? extends E> c) // 從指定位置開(kāi)始,將指定 collection 的所有元素插入此列表。 boolean addAll(int index, Collection<? extends E> c) // 按照指定 collection 的迭代器返回元素的順序,將指定 collection 中尚未包含在此列表中的所有元素添加列表的尾部。 int addAllAbsent(Collection<? extends E> c) // 添加元素(如果不存在)。 boolean addIfAbsent(E e) // 從此列表移除所有元素。 void clear() // 返回此列表的淺表副本。 Object clone() // 如果此列表包含指定的元素,則返回 true。 boolean contains(Object o) // 如果此列表包含指定 collection 的所有元素,則返回 true。 boolean containsAll(Collection<?> c) // 比較指定對(duì)象與此列表的相等性。 boolean equals(Object o) // 返回列表中指定位置的元素。 E get(int index) // 返回此列表的哈希碼值。 int hashCode() // 返回第一次出現(xiàn)的指定元素在此列表中的索引,從 index 開(kāi)始向前搜索,如果沒(méi)有找到該元素,則返回 -1。 int indexOf(E e, int index) // 返回此列表中第一次出現(xiàn)的指定元素的索引;如果此列表不包含該元素,則返回 -1。 int indexOf(Object o) // 如果此列表不包含任何元素,則返回 true。 boolean isEmpty() // 返回以恰當(dāng)順序在此列表元素上進(jìn)行迭代的迭代器。 Iterator<E> iterator() // 返回最后一次出現(xiàn)的指定元素在此列表中的索引,從 index 開(kāi)始向后搜索,如果沒(méi)有找到該元素,則返回 -1。 int lastIndexOf(E e, int index) // 返回此列表中最后出現(xiàn)的指定元素的索引;如果列表不包含此元素,則返回 -1。 int lastIndexOf(Object o) // 返回此列表元素的列表迭代器(按適當(dāng)順序)。 ListIterator<E> listIterator() // 返回列表中元素的列表迭代器(按適當(dāng)順序),從列表的指定位置開(kāi)始。 ListIterator<E> listIterator(int index) // 移除此列表指定位置上的元素。 E remove(int index) // 從此列表移除第一次出現(xiàn)的指定元素(如果存在)。 boolean remove(Object o) // 從此列表移除所有包含在指定 collection 中的元素。 boolean removeAll(Collection<?> c) // 只保留此列表中包含在指定 collection 中的元素。 boolean retainAll(Collection<?> c) // 用指定的元素替代此列表指定位置上的元素。 E set(int index, E element) // 返回此列表中的元素?cái)?shù)。 int size() // 返回此列表中 fromIndex(包括)和 toIndex(不包括)之間部分的視圖。 List<E> subList(int fromIndex, int toIndex) // 返回一個(gè)按恰當(dāng)順序(從第一個(gè)元素到最后一個(gè)元素)包含此列表中所有元素的數(shù)組。 Object[] toArray() // 返回以恰當(dāng)順序(從第一個(gè)元素到最后一個(gè)元素)包含列表所有元素的數(shù)組;返回?cái)?shù)組的運(yùn)行時(shí)類型是指定數(shù)組的運(yùn)行時(shí)類型。 <T> T[] toArray(T[] a) // 返回此列表的字符串表示形式。 String toString()
下面我們從“創(chuàng)建,添加,刪除,獲取,遍歷”這5個(gè)方面去分析CopyOnWriteArrayList的原理。
1. 創(chuàng)建
CopyOnWriteArrayList共3個(gè)構(gòu)造函數(shù)。它們的源碼如下:
public CopyOnWriteArrayList() {
setArray(new Object[0]);
}
public CopyOnWriteArrayList(Collection<? extends E> c) {
Object[] elements = c.toArray();
if (elements.getClass() != Object[].class)
elements = Arrays.copyOf(elements, elements.length, Object[].class);
setArray(elements);
}
public CopyOnWriteArrayList(E[] toCopyIn) {
setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class));
}
說(shuō)明:這3個(gè)構(gòu)造函數(shù)都調(diào)用了setArray(),setArray()的源碼如下:
private volatile transient Object[] array;
final Object[] getArray() {
return array;
}
final void setArray(Object[] a) {
array = a;
}
說(shuō)明:setArray()的作用是給array賦值;其中,array是volatile transient Object[]類型,即array是“volatile數(shù)組”。
關(guān)于volatile關(guān)鍵字,我們知道“volatile能讓變量變得可見(jiàn)”,即對(duì)一個(gè)volatile變量的讀,總是能看到(任意線程)對(duì)這個(gè)volatile變量最后的寫(xiě)入。正在由于這種特性,每次更新了“volatile數(shù)組”之后,其它線程都能看到對(duì)它所做的更新。
關(guān)于transient關(guān)鍵字,它是在序列化中才起作用,transient變量不會(huì)被自動(dòng)序列化。transient不是本文關(guān)注的重點(diǎn),了解即可。
2. 添加
以add(E e)為例,來(lái)對(duì)“CopyOnWriteArrayList的添加操作”進(jìn)行說(shuō)明。下面是add(E e)的代碼:
public boolean add(E e) {
final ReentrantLock lock = this.lock;
// 獲取“鎖”
lock.lock();
try {
// 獲取原始”volatile數(shù)組“中的數(shù)據(jù)和數(shù)據(jù)長(zhǎng)度。
Object[] elements = getArray();
int len = elements.length;
// 新建一個(gè)數(shù)組newElements,并將原始數(shù)據(jù)拷貝到newElements中;
// newElements數(shù)組的長(zhǎng)度=“原始數(shù)組的長(zhǎng)度”+1
Object[] newElements = Arrays.copyOf(elements, len + 1);
// 將“新增加的元素”保存到newElements中。
newElements[len] = e;
// 將newElements賦值給”volatile數(shù)組“。
setArray(newElements);
return true;
} finally {
// 釋放“鎖”
lock.unlock();
}
}
說(shuō)明:add(E e)的作用就是將數(shù)據(jù)e添加到”volatile數(shù)組“中。它的實(shí)現(xiàn)方式是,新建一個(gè)數(shù)組,接著將原始的”volatile數(shù)組“的數(shù)據(jù)拷貝到新數(shù)組中,然后將新增數(shù)據(jù)也添加到新數(shù)組中;最后,將新數(shù)組賦值給”volatile數(shù)組“。
在add(E e)中有兩點(diǎn)需要關(guān)注。
第一,在”添加操作“開(kāi)始前,獲取獨(dú)占鎖(lock),若此時(shí)有需要線程要獲取鎖,則必須等待;在操作完畢后,釋放獨(dú)占鎖(lock),此時(shí)其它線程才能獲取鎖。通過(guò)獨(dú)占鎖,來(lái)防止多線程同時(shí)修改數(shù)據(jù)!lock的定義如下:
transient final ReentrantLock lock = new ReentrantLock();
第二,操作完畢時(shí),會(huì)通過(guò)setArray()來(lái)更新”volatile數(shù)組“。而且,前面我們提過(guò)”即對(duì)一個(gè)volatile變量的讀,總是能看到(任意線程)對(duì)這個(gè)volatile變量最后的寫(xiě)入“;這樣,每次添加元素之后,其它線程都能看到新添加的元素。
3. 獲取
以get(int index)為例,來(lái)對(duì)“CopyOnWriteArrayList的刪除操作”進(jìn)行說(shuō)明。下面是get(int index)的代碼:
public E get(int index) {
return get(getArray(), index);
}
private E get(Object[] a, int index) {
return (E) a[index];
}
說(shuō)明:get(int index)的實(shí)現(xiàn)很簡(jiǎn)單,就是返回”volatile數(shù)組“中的第index個(gè)元素。
4. 刪除
以remove(int index)為例,來(lái)對(duì)“CopyOnWriteArrayList的刪除操作”進(jìn)行說(shuō)明。下面是remove(int index)的代碼:
public E remove(int index) {
final ReentrantLock lock = this.lock;
// 獲取“鎖”
lock.lock();
try {
// 獲取原始”volatile數(shù)組“中的數(shù)據(jù)和數(shù)據(jù)長(zhǎng)度。
Object[] elements = getArray();
int len = elements.length;
// 獲取elements數(shù)組中的第index個(gè)數(shù)據(jù)。
E oldValue = get(elements, index);
int numMoved = len - index - 1;
// 如果被刪除的是最后一個(gè)元素,則直接通過(guò)Arrays.copyOf()進(jìn)行處理,而不需要新建數(shù)組。
// 否則,新建數(shù)組,然后將”volatile數(shù)組中被刪除元素之外的其它元素“拷貝到新數(shù)組中;最后,將新數(shù)組賦值給”volatile數(shù)組“。
if (numMoved == 0)
setArray(Arrays.copyOf(elements, len - 1));
else {
Object[] newElements = new Object[len - 1];
System.arraycopy(elements, 0, newElements, 0, index);
System.arraycopy(elements, index + 1, newElements, index,
numMoved);
setArray(newElements);
}
return oldValue;
} finally {
// 釋放“鎖”
lock.unlock();
}
}
說(shuō)明:remove(int index)的作用就是將”volatile數(shù)組“中第index個(gè)元素刪除。它的實(shí)現(xiàn)方式是,如果被刪除的是最后一個(gè)元素,則直接通過(guò)Arrays.copyOf()進(jìn)行處理,而不需要新建數(shù)組。否則,新建數(shù)組,然后將”volatile數(shù)組中被刪除元素之外的其它元素“拷貝到新數(shù)組中;最后,將新數(shù)組賦值給”volatile數(shù)組“。
和add(E e)一樣,remove(int index)也是”在操作之前,獲取獨(dú)占鎖;操作完成之后,釋放獨(dú)占是“;并且”在操作完成時(shí),會(huì)通過(guò)將數(shù)據(jù)更新到volatile數(shù)組中“。
5. 遍歷
以iterator()為例,來(lái)對(duì)“CopyOnWriteArrayList的遍歷操作”進(jìn)行說(shuō)明。下面是iterator()的代碼:
public Iterator<E> iterator() {
return new COWIterator<E>(getArray(), 0);
}
說(shuō)明:iterator()會(huì)返回COWIterator對(duì)象。
COWIterator實(shí)現(xiàn)額ListIterator接口,它的源碼如下:
private static class COWIterator<E> implements ListIterator<E> {
private final Object[] snapshot;
private int cursor;
private COWIterator(Object[] elements, int initialCursor) {
cursor = initialCursor;
snapshot = elements;
}
public boolean hasNext() {
return cursor < snapshot.length;
}
public boolean hasPrevious() {
return cursor > 0;
}
// 獲取下一個(gè)元素
@SuppressWarnings("unchecked")
public E next() {
if (! hasNext())
throw new NoSuchElementException();
return (E) snapshot[cursor++];
}
// 獲取上一個(gè)元素
@SuppressWarnings("unchecked")
public E previous() {
if (! hasPrevious())
throw new NoSuchElementException();
return (E) snapshot[--cursor];
}
public int nextIndex() {
return cursor;
}
public int previousIndex() {
return cursor-1;
}
public void remove() {
throw new UnsupportedOperationException();
}
public void set(E e) {
throw new UnsupportedOperationException();
}
public void add(E e) {
throw new UnsupportedOperationException();
}
}
說(shuō)明:COWIterator不支持修改元素的操作。例如,對(duì)于remove(),set(),add()等操作,COWIterator都會(huì)拋出異常!
另外,需要提到的一點(diǎn)是,CopyOnWriteArrayList返回迭代器不會(huì)拋出ConcurrentModificationException異常,即它不是fail-fast機(jī)制的!
CopyOnWriteArrayList示例
下面,我們通過(guò)一個(gè)例子去對(duì)比ArrayList和CopyOnWriteArrayList。
import java.util.*;
import java.util.concurrent.*;
/*
* CopyOnWriteArrayList是“線程安全”的動(dòng)態(tài)數(shù)組,而ArrayList是非線程安全的。
*
* 下面是“多個(gè)線程同時(shí)操作并且遍歷list”的示例
* (01) 當(dāng)list是CopyOnWriteArrayList對(duì)象時(shí),程序能正常運(yùn)行。
* (02) 當(dāng)list是ArrayList對(duì)象時(shí),程序會(huì)產(chǎn)生ConcurrentModificationException異常。
*
*
*/
public class CopyOnWriteArrayListTest1 {
// TODO: list是ArrayList對(duì)象時(shí),程序會(huì)出錯(cuò)。
//private static List<String> list = new ArrayList<String>();
private static List<String> list = new CopyOnWriteArrayList<String>();
public static void main(String[] args) {
// 同時(shí)啟動(dòng)兩個(gè)線程對(duì)list進(jìn)行操作!
new MyThread("ta").start();
new MyThread("tb").start();
}
private static void printAll() {
String value = null;
Iterator iter = list.iterator();
while(iter.hasNext()) {
value = (String)iter.next();
System.out.print(value+", ");
}
System.out.println();
}
private static class MyThread extends Thread {
MyThread(String name) {
super(name);
}
@Override
public void run() {
int i = 0;
while (i++ < 6) {
// “線程名” + "-" + "序號(hào)"
String val = Thread.currentThread().getName()+"-"+i;
list.add(val);
// 通過(guò)“Iterator”遍歷List。
printAll();
}
}
}
}
(某一次)運(yùn)行結(jié)果:
ta-1, tb-1, ta-1, tb-1, ta-1, ta-1, tb-1, tb-1, tb-2, tb-2, ta-1, ta-2, tb-1, ta-1, tb-2, tb-1, ta-2, tb-2, tb-3, ta-2, ta-1, tb-3, tb-1, ta-3, tb-2, ta-1, ta-2, tb-1, tb-3, tb-2, ta-3, ta-2, tb-4, tb-3, ta-1, ta-3, tb-1, tb-4, tb-2, ta-4, ta-2, ta-1, tb-3, tb-1, ta-3, tb-2, tb-4, ta-2, ta-4, tb-3, tb-5, ta-3, ta-1, tb-4, tb-1, ta-4, tb-2, tb-5, ta-2, ta-5, tb-3, ta-1, ta-3, tb-1, tb-4, tb-2, ta-4, ta-2, tb-5, tb-3, ta-5, ta-3, tb-6, tb-4, ta-4, tb-5, ta-5, tb-6, ta-6,
結(jié)果說(shuō)明:如果將源碼中的list改成ArrayList對(duì)象時(shí),程序會(huì)產(chǎn)生ConcurrentModificationException異常。
相關(guān)文章
SpringMVC 攔截器不攔截靜態(tài)資源的三種處理方式方法
本篇文章主要介紹了SpringMVC 攔截器不攔截靜態(tài)資源的三種處理方式方法,詳細(xì)的介紹了三種方法,有興趣的可以了解一下。2017-01-01
Spring Security實(shí)現(xiàn)5次密碼錯(cuò)誤觸發(fā)賬號(hào)自動(dòng)鎖定功能
在現(xiàn)代互聯(lián)網(wǎng)應(yīng)用中,賬號(hào)安全是重中之重,然而,暴力 破解攻擊依然是最常見(jiàn)的安全威脅之一,攻擊者通過(guò)自動(dòng)化腳本嘗試大量的用戶名和密碼組合,試圖找到漏洞進(jìn)入系統(tǒng),所以為了解決這一問(wèn)題,賬號(hào)鎖定機(jī)制被廣泛應(yīng)用,本文介紹了Spring Security實(shí)現(xiàn)5次密碼錯(cuò)誤觸發(fā)賬號(hào)鎖定功能2024-12-12
Eclipse項(xiàng)目出現(xiàn)紅色嘆號(hào)的解決方法
eclipse工程前面出現(xiàn)紅色嘆號(hào)都是由于eclipse項(xiàng)目、eclipse工程中,缺少了一些jar包等文件引起的,這篇文章主要給大家介紹了關(guān)于Eclipse項(xiàng)目出現(xiàn)紅色嘆號(hào)的解決方法,需要的朋友可以參考下2023-11-11
Java中小球碰撞并使用按鈕控制數(shù)量實(shí)例代碼
這篇文章主要給大家介紹了關(guān)于Java中小球碰撞并使用按鈕控制數(shù)量的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2021-12-12
Java用POI解析excel并獲取所有單元格數(shù)據(jù)的實(shí)例
下面小編就為大家?guī)?lái)一篇Java用POI解析excel并獲取所有單元格數(shù)據(jù)的實(shí)例。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-10-10
Java報(bào)錯(cuò)sun.misc.Unsafe.park(Native Method)問(wèn)題
這篇文章主要介紹了Java報(bào)錯(cuò)sun.misc.Unsafe.park(Native Method)問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-07-07

