Java中的CopyOnWriteArrayList原理詳解
CopyOnWriteArrayList的原理是什么
CopyOnWriteArrayList是線程安全版本的ArrayList。
這里先上一小段源碼
final transient ReentrantLock lock = new ReentrantLock(); /** The array, accessed only via getArray/setArray. */ private transient volatile Object[] array; /** * Gets the array. Non-private so as to also be accessible * from CopyOnWriteArraySet class. */ final Object[] getArray() { return array; } /** * Sets the array. */ final void setArray(Object[] a) { array = a; } /** * Creates an empty list. */ public CopyOnWriteArrayList() { setArray(new Object[0]); }
如源碼所示,CopyOnWriteArrayList和ArrayList一樣,都在內(nèi)部維護(hù)了一個(gè)數(shù)組。操作CopyOnWriteArrayList其實(shí)就是在操作內(nèi)部的數(shù)組。
但關(guān)鍵是和ArrayList的不同之處
1) 使用volatile修飾內(nèi)部數(shù)組
private transient volatile Object[] array;
看這行代碼,使用volatile修飾了內(nèi)部數(shù)組 volatile關(guān)鍵字保證了每次拿到的內(nèi)部數(shù)組都是最新值。因?yàn)関olatile關(guān)鍵字表示直接去主存中獲取值,因此哪怕別的線程剛修改完內(nèi)部數(shù)組,也能保證獲取內(nèi)部數(shù)組時(shí)是最新的。
2) 加鎖
提到并發(fā)編程,當(dāng)然少不了加鎖。
final transient ReentrantLock lock = new ReentrantLock();
CopyOnWriteArrayList每創(chuàng)建一個(gè)實(shí)例,都會(huì)同時(shí)創(chuàng)建一個(gè)ReentrantLock鎖。 CopyOnWriteArrayList會(huì)在增,刪,改操作時(shí)添加鎖,而不會(huì)在讀操作時(shí)加鎖。
3) 使用COW思想操作數(shù)組。
COW即是CopyOnWrite的縮寫(xiě)。即每次在寫(xiě)入之前,先獲取源數(shù)據(jù)的拷貝,修改完拷貝后,再保存到源數(shù)據(jù)中。
get操作
private E get(Object[] a, int index) { return (E) a[index]; } public E get(int index) { return get(getArray(), index); }
如源碼所示,get操作沒(méi)有加鎖,直接返回?cái)?shù)組中的對(duì)象。
set操作
public E set(int index, E element) { final ReentrantLock lock = this.lock; lock.lock(); try { Object[] elements = getArray(); E oldValue = get(elements, index); if (oldValue != element) { int len = elements.length; Object[] newElements = Arrays.copyOf(elements, len); newElements[index] = element; setArray(newElements); } else { // Not quite a no-op; ensures volatile write semantics setArray(elements); } return oldValue; } finally { lock.unlock(); } }
大致流程:
- 加鎖
- 獲取源數(shù)組
- 判斷新值和舊值是否相同
- 不同的話拷貝源數(shù)組,更新值,然后更新源數(shù)組
- 相同的話,不更新值,然后更新源數(shù)組(數(shù)組內(nèi)容沒(méi)變)
- 釋放鎖
add操作
public boolean add(E e) { final ReentrantLock lock = this.lock; lock.lock(); try { Object[] elements = getArray(); int len = elements.length; Object[] newElements = Arrays.copyOf(elements, len + 1); newElements[len] = e; setArray(newElements); return true; } finally { lock.unlock(); } }
大致流程:
- 加鎖
- 獲取源數(shù)組
- 復(fù)制一個(gè)源數(shù)組長(zhǎng)度+1的新數(shù)組
- 在數(shù)組末尾賦值,然后更新源數(shù)組
- 釋放鎖
remove操作
public E remove(int index) { final ReentrantLock lock = this.lock; lock.lock(); try { Object[] elements = getArray(); int len = elements.length; E oldValue = get(elemnts, index); int numMoved = len - index - 1; 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(); } }
大致流程:
- 加鎖
- 獲取源數(shù)組
- 判斷刪除的位置是不是數(shù)組末尾
- 是末尾的話,復(fù)制一個(gè)數(shù)組長(zhǎng)度減一的數(shù)組,然后更新源數(shù)組
- 不是末尾的話,做成一個(gè)不包含需要?jiǎng)h除的元素的新數(shù)組,然后更新源數(shù)組
- 釋放鎖
以上操作可以看出, 查操作不加鎖 增刪改操作大致流程都是一樣的,先加鎖,然后復(fù)制一份源數(shù)組,操作完后寫(xiě)入源數(shù)組,釋放鎖。
那么為什么要這么做呢?都已經(jīng)加鎖了,為什么不能直接操作源數(shù)組呢?不然加鎖是為了什么? 這是我第一次看到這種做法時(shí)的疑問(wèn)。接下來(lái)一一解釋。
讀操作為什么不加鎖
當(dāng)然是為了提高讀操作的效率啦
既然加鎖了為什么不能直接操作源數(shù)組?
因?yàn)樽x操作沒(méi)有加鎖。增刪改操作時(shí),讀操作可以在任何一步時(shí)獲取數(shù)組里的值。 如果剛生成一個(gè)新數(shù)組,還沒(méi)有更新里面的值的情況下就被執(zhí)行了讀操作,就會(huì)出現(xiàn)不可預(yù)料的情況。
因此為了保證數(shù)據(jù)的最終一致性。只有當(dāng)數(shù)組完全更新結(jié)束后,再刷新源數(shù)組的值,才能保證讀取的要么是舊值,要么是最新值。
既然使用COW就可以保證讀操作不出現(xiàn)異常,那為什么還要加鎖?
加鎖是為了保證和其他寫(xiě)操作不沖突。
CopyOnWriteArrayList的優(yōu)缺點(diǎn)
優(yōu)點(diǎn): 在保證線程安全的情況下,可以獲得非常高效的讀操作。 雖然寫(xiě)操作性能低下,但能保證線程安全。
缺點(diǎn): 因?yàn)槊看螌?xiě)操作都需要復(fù)制一份新數(shù)組,所以寫(xiě)操作性能低下,尤其是數(shù)組長(zhǎng)度很長(zhǎng)時(shí),不建議使用CopyOnWriteArrayList。
CopyOnWriteArrayList的應(yīng)用場(chǎng)景
高并發(fā)場(chǎng)景,多讀取,少寫(xiě)入。
到此這篇關(guān)于Java中的CopyOnWriteArrayList原理詳解的文章就介紹到這了,更多相關(guān)CopyOnWriteArrayList原理內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringCloud之熔斷監(jiān)控Hystrix Dashboard的實(shí)現(xiàn)
這篇文章主要介紹了SpringCloud之熔斷監(jiān)控Hystrix Dashboard的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-09-09Spring boot通過(guò)AOP防止API重復(fù)請(qǐng)求代碼實(shí)例
這篇文章主要介紹了Spring boot通過(guò)AOP防止API重復(fù)請(qǐng)求代碼實(shí)例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-12-12Springboot整合PageOffice 實(shí)現(xiàn)word在線編輯保存功能
這篇文章主要介紹了Springboot整合PageOffice 實(shí)現(xiàn)word在線編輯保存,本文以Samples5 為示例文件結(jié)合示例代碼給大家詳細(xì)介紹,需要的朋友可以參考下2021-08-08Java中invokedynamic字節(jié)碼指令問(wèn)題
這篇文章主要介紹了Java中invokedynamic字節(jié)碼指令問(wèn)題,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-04-04java中BigDecimal進(jìn)行加減乘除的基本用法
大家應(yīng)該對(duì)于不需要任何準(zhǔn)確計(jì)算精度的數(shù)字可以直接使用float或double運(yùn)算,但是如果需要精確計(jì)算的結(jié)果,則必須使用BigDecimal類(lèi),而且使用BigDecimal類(lèi)也可以進(jìn)行大數(shù)的操作。下面這篇文章就給大家介紹介紹關(guān)于java中BigDecimal進(jìn)行加減乘除的基本用法。2016-12-12Selenium Webdriver實(shí)現(xiàn)截圖功能的示例
今天小編就為大家分享一篇Selenium Webdriver實(shí)現(xiàn)截圖功能的示例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-05-05java保留小數(shù)的四種實(shí)現(xiàn)方法
這篇文章主要為大家詳細(xì)介紹了java保留小數(shù)的四種實(shí)現(xiàn)方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-11-11Java分別利用深度優(yōu)先和廣度優(yōu)先求解迷宮路徑
這篇文章主要為大家詳細(xì)介紹了Java如何利用深度優(yōu)先的非遞歸遍歷方法和廣度優(yōu)先的遍歷方法實(shí)現(xiàn)求解迷宮路徑,文中的示例代碼講解詳細(xì),需要的可以參考一下2022-08-08java動(dòng)態(tài)目錄樹(shù)的實(shí)現(xiàn)示例
在開(kāi)發(fā)過(guò)程中,常常需要對(duì)目錄結(jié)構(gòu)進(jìn)行操作和展示,本文主要介紹了java動(dòng)態(tài)目錄樹(shù)的實(shí)現(xiàn)示例,具有一定的參考價(jià)值,感興趣的可以了解一下2024-03-03