java并發(fā)容器CopyOnWriteArrayList實(shí)現(xiàn)原理及源碼分析
CopyOnWriteArrayList是Java并發(fā)包中提供的一個(gè)并發(fā)容器,它是個(gè)線程安全且讀操作無鎖的ArrayList,寫操作則通過創(chuàng)建底層數(shù)組的新副本來實(shí)現(xiàn),是一種讀寫分離的并發(fā)策略,我們也可以稱這種容器為"寫時(shí)復(fù)制器",Java并發(fā)包中類似的容器還有CopyOnWriteSet。本文會(huì)對CopyOnWriteArrayList的實(shí)現(xiàn)原理及源碼進(jìn)行分析。
實(shí)現(xiàn)原理
我們都知道,集合框架中的ArrayList是非線程安全的,Vector雖是線程安全的,但由于簡單粗暴的鎖同步機(jī)制,性能較差。而CopyOnWriteArrayList則提供了另一種不同的并發(fā)處理策略(當(dāng)然是針對特定的并發(fā)場景)。
很多時(shí)候,我們的系統(tǒng)應(yīng)對的都是讀多寫少的并發(fā)場景。CopyOnWriteArrayList容器允許并發(fā)讀,讀操作是無鎖的,性能較高。至于寫操作,比如向容器中添加一個(gè)元素,則首先將當(dāng)前容器復(fù)制一份,然后在新副本上執(zhí)行寫操作,結(jié)束之后再將原容器的引用指向新容器。
優(yōu)缺點(diǎn)分析
了解了CopyOnWriteArrayList的實(shí)現(xiàn)原理,分析它的優(yōu)缺點(diǎn)及使用場景就很容易了。
優(yōu)點(diǎn):
讀操作性能很高,因?yàn)闊o需任何同步措施,比較適用于讀多寫少的并發(fā)場景。Java的list在遍歷時(shí),若中途有別的線程對list容器進(jìn)行修改,則會(huì)拋出ConcurrentModificationException異常。而CopyOnWriteArrayList由于其"讀寫分離"的思想,遍歷和修改操作分別作用在不同的list容器,所以在使用迭代器進(jìn)行遍歷時(shí)候,也就不會(huì)拋出ConcurrentModificationException異常了
缺點(diǎn):
缺點(diǎn)也很明顯,一是內(nèi)存占用問題,畢竟每次執(zhí)行寫操作都要將原容器拷貝一份,數(shù)據(jù)量大時(shí),對內(nèi)存壓力較大,可能會(huì)引起頻繁GC;二是無法保證實(shí)時(shí)性,Vector對于讀寫操作均加鎖同步,可以保證讀和寫的強(qiáng)一致性。而CopyOnWriteArrayList由于其實(shí)現(xiàn)策略的原因,寫和讀分別作用在新老不同容器上,在寫操作執(zhí)行過程中,讀不會(huì)阻塞但讀取到的卻是老容器的數(shù)據(jù)。
源碼分析
基本原理了解了,CopyOnWriteArrayList的代碼實(shí)現(xiàn)看起來就很容易理解了。
public boolean add(E e) { //ReentrantLock加鎖,保證線程安全 final ReentrantLock lock = this.lock; lock.lock(); try { Object[] elements = getArray(); int len = elements.length; //拷貝原容器,長度為原容器長度加一 Object[] newElements = Arrays.copyOf(elements, len + 1); //在新副本上執(zhí)行添加操作 newElements[len] = e; //將原容器引用指向新副本 setArray(newElements); return true; } finally { //解鎖 lock.unlock(); } }
添加的邏輯很簡單,先將原容器copy一份,然后在新副本上執(zhí)行寫操作,之后再切換引用。當(dāng)然此過程是要加鎖的。
刪除操作
public E remove(int index) { //加鎖 final ReentrantLock lock = this.lock; lock.lock(); try { Object[] elements = getArray(); int len = elements.length; E oldValue = get(elements, index); int numMoved = len - index - 1; if (numMoved == 0) //如果要?jiǎng)h除的是列表末端數(shù)據(jù),拷貝前l(fā)en-1個(gè)數(shù)據(jù)到新副本上,再切換引用 setArray(Arrays.copyOf(elements, len - 1)); else { //否則,將除要?jiǎng)h除元素之外的其他元素拷貝到新副本中,并切換引用 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(); } }
刪除操作同理,將除要?jiǎng)h除元素之外的其他元素拷貝到新副本中,然后切換引用,將原容器引用指向新副本。同屬寫操作,需要加鎖。
我們再來看看讀操作,CopyOnWriteArrayList的讀操作是不用加鎖的,性能很高。
public E get(int index) { return get(getArray(), index); } 直接讀取即可,無需加鎖 private E get(Object[] a, int index) { return (E) a[index]; }
總結(jié)
本文對CopyOnWriteArrayList的實(shí)現(xiàn)原理和源碼進(jìn)行了分析,并對CopyOnWriteArrayList的優(yōu)缺點(diǎn)也進(jìn)行了分析(Java并發(fā)包中還提供了CopyOnWriteSet,原理類似)。其實(shí)所謂并發(fā)容器的優(yōu)缺點(diǎn),無非是取決于我們在面對特定并發(fā)場景時(shí),是否能做出相對合理的選擇和應(yīng)用。也希望本文能幫助到有需要的童鞋,共勉。
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
MyBatis實(shí)現(xiàn)插入大量數(shù)據(jù)方法詳解
最近在公司項(xiàng)目開發(fā)中遇到批量數(shù)據(jù)插入或者更新,下面這篇文章主要給大家介紹了關(guān)于MyBatis實(shí)現(xiàn)批量插入的相關(guān)資料,需要的朋友可以參考下2022-11-11JDBC操作數(shù)據(jù)庫的增加、刪除、更新、查找實(shí)例分析
這篇文章主要介紹了JDBC操作數(shù)據(jù)庫的增加、刪除、更新、查找方法,以完整實(shí)例形式分析了Java基于JDBC連接數(shù)據(jù)庫及進(jìn)行數(shù)據(jù)的增刪改查等技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-10-10用Java代碼實(shí)現(xiàn)棧數(shù)據(jù)結(jié)構(gòu)的基本方法歸納
這篇文章主要介紹了用Java代碼實(shí)現(xiàn)棧數(shù)據(jù)結(jié)構(gòu)的基本方法歸納,各種算法的實(shí)現(xiàn)也是ACM上經(jīng)常出現(xiàn)的題目,是計(jì)算機(jī)學(xué)習(xí)的基本功,需要的朋友可以參考下2015-08-08Java?IO及BufferedReader.readline()出現(xiàn)的Bug
這篇文章主要介紹了Java?IO及BufferedReader.readline()出現(xiàn)的Bug,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-12-12MyBatis查詢數(shù)據(jù),賦值給List集合時(shí),數(shù)據(jù)缺少的問題及解決
這篇文章主要介紹了MyBatis查詢數(shù)據(jù),賦值給List集合時(shí),數(shù)據(jù)缺少的問題及解決方案,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-01-01Xml中使用foreach遍歷對象實(shí)現(xiàn)代碼
這篇文章主要介紹了Xml中使用foreach遍歷對象實(shí)現(xiàn)代碼,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-12-12如何解決Spring事務(wù)注解@Transactional在類內(nèi)部方法調(diào)用不生效
這篇文章主要介紹了如何解決Spring事務(wù)注解@Transactional在類內(nèi)部方法調(diào)用不生效問題,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-08-08SpringBoot整合Jasypt實(shí)現(xiàn)配置加密的步驟詳解
Jasypt是一個(gè)Java庫,提供了一種簡單的加密解密方式,可用于保護(hù)敏感數(shù)據(jù),例如密碼、API密鑰和數(shù)據(jù)庫連接信息等,本文給大家介紹了SpringBoot整合Jasypt實(shí)現(xiàn)配置加密的詳細(xì)步驟,感興趣的同學(xué)可以參考一下2023-11-11