Java中的線程安全集合CopyOnWriteArrayList解析
CopyOnWriteArrayList
CopyOnWriteArrayList是ArrayList的線程安全版本,從他的名字可以推測(cè),CopyOnWriteArrayList是在有寫操作的時(shí)候會(huì)copy一份數(shù)據(jù),然后寫完再設(shè)置成新的數(shù)據(jù)。
CopyOnWriteArrayList適用于讀多寫少的并發(fā)場(chǎng)景,CopyOnWriteArraySet是線程安全版本的Set實(shí)現(xiàn),它的內(nèi)部通過(guò)一個(gè)CopyOnWriteArrayList來(lái)代理讀寫等操作,使得CopyOnWriteArraySet表現(xiàn)出了和CopyOnWriteArrayList一致的并發(fā)行為,他們的區(qū)別在于數(shù)據(jù)結(jié)構(gòu)模型的不同,set不允許多個(gè)相同的元素插入容器中,具體的細(xì)節(jié)將在下文中分析。
可以看到它實(shí)現(xiàn)了List接口,如果去看ArrayList的類圖的話,可以發(fā)現(xiàn)也是實(shí)現(xiàn)了List接口,也就得出一句廢話,ArrayList提供的api,CopyOnWriteArrayList也提供
下文中來(lái)分析CopyOnWriteArrayList是如何來(lái)做到線程安全的實(shí)現(xiàn)讀寫數(shù)據(jù)的,而且也會(huì)順便對(duì)比ArrayList的等效實(shí)現(xiàn)為什么不支持線程安全的。
下面首先展示了CopyOnWriteArrayList中比較重要的成員:
/** The lock protecting all mutators */ final transient ReentrantLock lock = new ReentrantLock(); /** The array, accessed only via getArray/setArray. */ private transient volatile Object[] array;
可以看到,CopyOnWriteArrayList使用了ReentrantLock來(lái)支持并發(fā)操作,array就是實(shí)際存放數(shù)據(jù)的數(shù)組對(duì)象。ReentrantLock是一種支持重入的獨(dú)占鎖,任意時(shí)刻只允許一個(gè)線程獲得鎖,所以可以安全的并發(fā)去寫數(shù)組,關(guān)于java中鎖的細(xì)節(jié),可以參考文章Java可重入鎖詳解。接下來(lái)看一下CopyOnWriteArrayList是如何使用這個(gè)lock來(lái)實(shí)現(xiàn)并發(fā)寫的,下面首先展示了add方法的代碼:
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock(); //上鎖,只允許一個(gè)線程進(jìn)入
try {
Object[] elements = getArray(); // 獲得當(dāng)前數(shù)組對(duì)象
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);//拷貝到一個(gè)新的數(shù)組中
newElements[len] = e;//插入數(shù)據(jù)元素
setArray(newElements);//將新的數(shù)組對(duì)象設(shè)置回去
return true;
} finally {
lock.unlock();//釋放鎖
}
}為了對(duì)比ArrayList,下面展示了ArrayList中的add方法的細(xì)節(jié):
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
} 相比CopyOnWriteArrayList,ArrayList的add方法實(shí)現(xiàn)就顯得啰嗦的多,而且ArrayList并不支持線程安全,至于為什么不支持線程安全,看代碼就知道了,這幾個(gè)調(diào)用的方法中都沒(méi)有類似鎖(與鎖等效語(yǔ)義的組件)出現(xiàn)。下面再來(lái)看另一個(gè)版本的add方法:
public void add(int index, E element) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
if (index > len || index < 0)
throw new IndexOutOfBoundsException("Index: "+index+
", Size: "+len);
Object[] newElements;
int numMoved = len - index;
if (numMoved == 0)
newElements = Arrays.copyOf(elements, len + 1);
else {
newElements = new Object[len + 1];
System.arraycopy(elements, 0, newElements, 0, index);
System.arraycopy(elements, index, newElements, index + 1,
numMoved);
}
newElements[index] = element;
setArray(newElements);
} finally {
lock.unlock();
}
}在操作之前都是先lock住的,這里面有一個(gè)有意思的地方,因?yàn)樵摲椒梢灾付╥ndex來(lái)插入value,如果這個(gè)index位置上已經(jīng)有舊值,那么該方法的作用類似replace,如果該index為當(dāng)前數(shù)組的長(zhǎng)度,那么該方法和上面分析的add方法等效,現(xiàn)在分析一下index位置上已經(jīng)有值的情況,會(huì)分為兩段copy,然后在中間設(shè)置新值?,F(xiàn)在來(lái)分析一下讀操作,下面是get方法的細(xì)節(jié):
public E get(int index) {
return get(getArray(), index);
}
private E get(Object[] a, int index) {
return (E) a[index];
}可以發(fā)現(xiàn)是非常簡(jiǎn)單的,而且讀是允許多個(gè)線程進(jìn)入的。下面來(lái)分析一下CopyOnWriteArrayList提高的迭代器。下面是兩個(gè)重要的變量:
/** Snapshot of the array */
private final Object[] snapshot;
/** Index of element to be returned by subsequent call to next. */
private int cursor;
遍歷的時(shí)候首先會(huì)獲得當(dāng)前數(shù)組對(duì)象的一個(gè)拷貝,稱為快照,然后遍歷的操作會(huì)在該快照上進(jìn)行,那如果獲取了迭代器之后再對(duì)CopyOnWriteArrayList進(jìn)行寫操作會(huì)怎么樣?迭代器能感知到這種變化嗎?下面實(shí)際實(shí)驗(yàn)一下:
CopyOnWriteArrayList<String> copyOnWriteArrayList = new CopyOnWriteArrayList<>();
copyOnWriteArrayList.add("first");
copyOnWriteArrayList.add("second");
Iterator<String> iterator = copyOnWriteArrayList.iterator();
copyOnWriteArrayList.add("third");
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
//output:
first
second結(jié)果是不能感知,也就是說(shuō),這個(gè)快照并不會(huì)和外界有任何聯(lián)系,某個(gè)線程在獲取迭代器的時(shí)候就會(huì)拷貝一份,或者說(shuō),每一個(gè)線程都將獲得當(dāng)前時(shí)刻的一個(gè)快照,所以不需要加鎖就可以安全的實(shí)現(xiàn)遍歷,下面的代碼也證實(shí)了上面的說(shuō)法:
public Iterator<E> iterator() {
return new COWIterator<E>(getArray(), 0);
}
CopyOnWriteArraySet
CopyOnWriteArraySet使用一個(gè)CopyOnWriteArrayList來(lái)做代理,它的所有api都是依賴于CopyOnWriteArrayList來(lái)實(shí)現(xiàn)的,下面的代碼也展示了這種代理的事實(shí): private final CopyOnWriteArrayList al;
/**
* Creates an empty set.
*/
public CopyOnWriteArraySet() {
al = new CopyOnWriteArrayList<E>();
}
下面來(lái)分析一下CopyOnWriteArraySet的寫操作實(shí)現(xiàn),比如add方法:
public boolean add(E e) {
return al.addIfAbsent(e);
}
public boolean addIfAbsent(E e) {
Object[] snapshot = getArray();
return indexOf(e, snapshot, 0, snapshot.length) >= 0 ? false :
addIfAbsent(e, snapshot);
}
private boolean addIfAbsent(E e, Object[] snapshot) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] current = getArray();
int len = current.length;
if (snapshot != current) {
// Optimize for lost race to another addXXX operation
int common = Math.min(snapshot.length, len);
for (int i = 0; i < common; i++)
if (current[i] != snapshot[i] && eq(e, current[i]))
return false;
if (indexOf(e, current, common, len) >= 0)
return false;
}
Object[] newElements = Arrays.copyOf(current, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
} set是一種不允許有重復(fù)元素的簡(jiǎn)單數(shù)據(jù)結(jié)構(gòu),所以和CopyOnWriteArrayList不同,CopyOnWriteArraySet需要add在插入新元素的時(shí)候多做一些判斷,而CopyOnWriteArraySet在實(shí)現(xiàn)上使用了CopyOnWriteArrayList的addIfAbsent方法,這個(gè)方法的意思就是如果存在就不再插入,如果不存在再進(jìn)行插入。
本人分析了CopyOnWriteArrayList的實(shí)現(xiàn)細(xì)節(jié),并且分析了基于CopyOnWriteArrayList實(shí)現(xiàn)的CopyOnWriteArraySet,介于CopyOnWriteArrayList的簡(jiǎn)單性,本文沒(méi)有太多亮點(diǎn),但是理解CopyOnWriteArrayList的實(shí)現(xiàn)細(xì)節(jié)是有必要的,在并發(fā)環(huán)境下,我們?cè)谶x擇對(duì)象容器的時(shí)候需要考量是否需要選擇線程安全的容器,如果不需要,則優(yōu)先選擇ArrayList等沒(méi)有線程安全保障的容器,如果需要線程安全保障,那么必須選擇類似CopyOnWriteArrayList的線程安全的容器集合,否則會(huì)造成不可預(yù)料的錯(cuò)誤。當(dāng)然,實(shí)現(xiàn)線程安全的代價(jià)是以損失部分性能為代價(jià)的,畢竟有l(wèi)ock-unlock的操作,但是這又是必須的。接下來(lái)的文章會(huì)分析一些java中實(shí)現(xiàn)的線程安全的容器,比如ConcurrentHashMap等,當(dāng)然,也會(huì)對(duì)類似HashMap之類的非線程安全的容器集合進(jìn)行分析總結(jié),畢竟類似HashMap這樣的容器集合是我們經(jīng)常使用的,理解他的具體實(shí)現(xiàn)有助于我們更好的使用它。
到此這篇關(guān)于Java中的線程安全集合CopyOnWriteArrayList解析的文章就介紹到這了,更多相關(guān)Java的CopyOnWriteArrayList內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java在讀取文件內(nèi)容的時(shí)候,如何判斷出空白行的操作
這篇文章主要介紹了Java在讀取文件內(nèi)容的時(shí)候,如何判斷出空白行的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-09-09
JVM內(nèi)存管理:OutOfMemoryError異常的問(wèn)題
這篇文章主要介紹了JVM內(nèi)存管理:OutOfMemoryError異常的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2025-03-03
spring AOP自定義注解方式實(shí)現(xiàn)日志管理的實(shí)例講解
下面小編就為大家分享一篇spring AOP自定義注解方式實(shí)現(xiàn)日志管理的實(shí)例講解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-01-01
Java Idea TranslationPlugin翻譯插件使用解析
這篇文章主要介紹了Java Idea TranslationPlugin翻譯插件使用解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-04-04
強(qiáng)烈推薦IDEA提高開(kāi)發(fā)效率的必備插件
這篇文章主要介紹了強(qiáng)烈推薦IDEA提高開(kāi)發(fā)效率的必備插件,文中有非常詳細(xì)的圖文示例,對(duì)想要提高企業(yè)開(kāi)發(fā)效率的小伙伴們有非常好的幫助,需要的朋友可以參考下2021-04-04
SpringBoot接口返回結(jié)果封裝方法實(shí)例詳解
在實(shí)際項(xiàng)目中,一般會(huì)把結(jié)果放在一個(gè)封裝類中,封裝類中包含http狀態(tài)值,狀態(tài)消息,以及實(shí)際的數(shù)據(jù)。這里主要記錄兩種方式,通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友參考下吧2021-09-09

