Java中的拷貝數(shù)組CopyOnWriteArrayList詳解
CopyOnWriteArrayList詳解
ArrayList和LinkedList都不是線程安全的,如果需要線程安全的List,可以使用Collections.synchronizedList來(lái)生成一個(gè)同步list,但是這個(gè)同步list的方法都是通過(guò)synchronized修飾來(lái)保證同步的,并發(fā)性能不高。那么如何提高并發(fā)性能呢?比如某些場(chǎng)景下,對(duì)List的讀操作遠(yuǎn)多于寫(xiě)操作,那么CopyOnWriteArrayList就派上用場(chǎng)了。
1、屬性
/** The lock protecting all mutators */ final transient ReentrantLock lock = new ReentrantLock(); /** The array, accessed only via getArray/setArray. */ private transient volatile Object[] array;
lock是用來(lái)在寫(xiě)操作時(shí)加鎖使用的,具體使用下面方法部分再看。
數(shù)組array是CopyOnWriteArrayList的核心部分了,所有的元素都存放在這個(gè)數(shù)組中。為了保證可見(jiàn)性,所以被volatile修飾著。
2、方法
final Object[] getArray() { return array; } final void setArray(Object[] a) { array = a; } public CopyOnWriteArrayList() { setArray(new Object[0]); } public CopyOnWriteArrayList(Collection<? extends E> c) { Object[] elements; if (c.getClass() == CopyOnWriteArrayList.class) elements = ((CopyOnWriteArrayList<?>)c).getArray(); else { elements = c.toArray(); // c.toArray might (incorrectly) not return Object[] (see 6260652) 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)); }
三個(gè)構(gòu)造函數(shù)都是為了給array數(shù)組賦值,生成初始數(shù)組。
下面看下它的幾個(gè)關(guān)鍵方法:get、add、remove
private E get(Object[] a, int index) { return (E) a[index]; } /** * {@inheritDoc} * * @throws IndexOutOfBoundsException {@inheritDoc} */ public E get(int index) { return get(getArray(), index); }
get比較簡(jiǎn)單,就是直接取數(shù)組索引處的元素。注意get方法沒(méi)有加鎖。
public boolean add(E e) { final ReentrantLock lock = this.lock; //1、加鎖 lock.lock(); try { //2、取原數(shù)組 Object[] elements = getArray(); int len = elements.length; //3、拷貝生成新數(shù)組 Object[] newElements = Arrays.copyOf(elements, len + 1); //4、新元素加到數(shù)組最后一位 newElements[len] = e; //5、數(shù)組替換 setArray(newElements); return true; } finally { //6、釋放鎖 lock.unlock(); } }
add方法,上來(lái)就先加鎖,然后取出原數(shù)組后拷貝生成了一個(gè)新的數(shù)組,注意,此時(shí)原有的array數(shù)組沒(méi)有變,get訪問(wèn)時(shí)還是跟之前一樣。當(dāng)把新的數(shù)組替換掉array后,由于是volatile修飾的,get訪問(wèn)時(shí)就會(huì)訪問(wèn)添加過(guò)元素的新數(shù)組。這樣就保證了讀寫(xiě)同時(shí)進(jìn)行時(shí),讀不需要加鎖依然不會(huì)有并發(fā)問(wèn)題。最后釋放鎖后,別的寫(xiě)操作獲得鎖,再次進(jìn)行替換操作,這樣保證寫(xiě)操作與寫(xiě)操作之間不會(huì)有并發(fā)問(wèn)題。
public E remove(int index) { final ReentrantLock lock = this.lock; //1、獲取鎖 lock.lock(); try { //2、取原數(shù)組 Object[] elements = getArray(); int len = elements.length; E oldValue = get(elements, index); int numMoved = len - index - 1; //3、如果被刪除元素是數(shù)組最后一位,直接截取len-1的新數(shù)組 if (numMoved == 0) setArray(Arrays.copyOf(elements, len - 1)); //4、否則,分段拷貝生成新數(shù)組 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 { //5、釋放鎖 lock.unlock(); } }
remove與add同理,也是先獲取鎖,同時(shí)生成新的數(shù)組之后再把原有的數(shù)組進(jìn)行替換。
3、總結(jié)
- 所有的元素均存儲(chǔ)到數(shù)組中
- 寫(xiě)操作(add/set/remove等)會(huì)改變數(shù)組結(jié)構(gòu),采用新生成一個(gè)數(shù)組然后進(jìn)行替換的做法,保證了在此期間原數(shù)組可以正常訪問(wèn)。同時(shí)操作之前需要先獲取鎖,避免寫(xiě)操作之間產(chǎn)生并發(fā)問(wèn)題。
- 讀操作由于不需要改變數(shù)組結(jié)構(gòu),且寫(xiě)操作時(shí),對(duì)原有的數(shù)組不進(jìn)行修改,此時(shí)仍可正常讀取。寫(xiě)操作將新數(shù)組進(jìn)行替換后,由于數(shù)組被volatile修飾,保證了可見(jiàn)性,此時(shí)也可正正常讀取。所以讀操作不需要加鎖。
- 因?yàn)槊看螌?xiě)操作都會(huì)帶來(lái)數(shù)組拷貝,所以當(dāng)讀操作遠(yuǎn)大于寫(xiě)操作時(shí),才可考慮使用此容器。
到此這篇關(guān)于Java中的拷貝數(shù)組CopyOnWriteArrayList詳解的文章就介紹到這了,更多相關(guān)CopyOnWriteArrayList詳解內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot整合redis中的JSON序列化文件夾操作小結(jié)
在我們?nèi)粘5捻?xiàng)目開(kāi)發(fā)中,使用redis作為緩存,來(lái)提高系統(tǒng)訪問(wèn)速度和緩解系統(tǒng)壓力,在使用中遇到幾個(gè)問(wèn)題,本文給大家詳細(xì)總結(jié)下,對(duì)SpringBoot整合redis?JSON序列化相關(guān)知識(shí)感興趣的朋友一起看看吧2022-02-02Java?Spring?事件監(jiān)聽(tīng)詳情解析
這篇文章主要介紹了Java?Spring?事件監(jiān)聽(tīng)詳情解析,文章圍繞主題展開(kāi)詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下2022-07-07eclipse創(chuàng)建一個(gè)基于maven的web項(xiàng)目詳細(xì)步驟
開(kāi)始學(xué)習(xí)maven,并用maven創(chuàng)建了第一個(gè)屬于自己的web項(xiàng)目,下面這篇文章主要給大家介紹了關(guān)于eclipse創(chuàng)建一個(gè)基于maven的web項(xiàng)目的相關(guān)資料,文中通過(guò)圖文介紹的非常詳細(xì),需要的朋友可以參考下2023-12-12IDEA中Maven依賴包無(wú)法下載或?qū)氲慕鉀Q方案(系統(tǒng)缺失文件導(dǎo)致)
在配置Maven環(huán)境時(shí),可能會(huì)遇到各種報(bào)錯(cuò)問(wèn)題,首先確保Maven路徑配置正確,例如使用apache-maven-3.5.0版本,則需要在系統(tǒng)環(huán)境變量的Path中添加其bin目錄路徑,并上移優(yōu)先級(jí),接下來(lái),在Maven的conf目錄下修改settings.xml文件,將鏡像源改為阿里云2024-09-09Java 反射機(jī)制知識(shí)詳細(xì)介紹及總結(jié)
反射機(jī)制是在運(yùn)行狀態(tài)中,對(duì)于任意一個(gè)類(lèi),都能夠知道這個(gè)類(lèi)的所有屬性和方法;對(duì)于任意一個(gè)對(duì)象,都能夠調(diào)用它的任意一個(gè)方法和屬性;這種動(dòng)態(tài)獲取的信息以及動(dòng)態(tài)調(diào)用對(duì)象的方法的功能稱(chēng)為java語(yǔ)言的反射機(jī)制2017-01-01Java解決線程的不安全問(wèn)題之volatile關(guān)鍵字詳解
這篇文章主要介紹了Java解決線程的不安全問(wèn)題之volatile關(guān)鍵字詳解,可見(jiàn)性指一個(gè)線程對(duì)共享變量值的修改,能夠及時(shí)地被其他線程看到,而 volatile 關(guān)鍵字就保證內(nèi)存的可見(jiàn)性,需要的朋友可以參考下2023-08-08