詳解CopyOnWriteArrayList是如何保證線(xiàn)程安全
一:前言
在我們需要保證線(xiàn)程安全的時(shí)候,如果使用到Map,那么我們可以使用線(xiàn)程安全的ConcurrentHashMap
,ConcurrentHashMap
不僅可以保證線(xiàn)程安全,而且效率也非常不錯(cuò),那有沒(méi)有線(xiàn)程安全的List呢?
答案是有,那就是CopyOnWriteArrayList。今天我們就一起來(lái)了解一下CopyOnWriteArrayList,看它是如何巧妙的保證線(xiàn)程安全的吧。
二:成員變量分析
//進(jìn)行修改操作時(shí)的鎖 final transient ReentrantLock lock = new ReentrantLock(); //真正保存數(shù)據(jù)的數(shù)組 用volatile關(guān)鍵字進(jìn)行修飾,保證array的引用的可見(jiàn)性 private transient volatile Object[] array;
三:源碼分析
首先我們看構(gòu)造方法,CopyOnWriteArrayList有三個(gè)構(gòu)造方法。
1.空參構(gòu)造
調(diào)用setArray方法將成員變量array賦值為一個(gè)長(zhǎng)度為0的數(shù)組。
public CopyOnWriteArrayList() { setArray(new Object[0]); }
final void setArray(Object[] a) { array = a; }
2.傳入一個(gè)Collection對(duì)象的構(gòu)造方法
首先判斷Collection是否是一個(gè)CopyOnWriteArrayList,如果是,直接將傳入的CopyOnWriteArrayList的elements重新賦值給需要?jiǎng)?chuàng)建的CopyOnWriteArrayList。
如果不是,判斷Collection是否是ArrayList,如果是,那么就利用toArray()方法將其轉(zhuǎn)化為一個(gè)數(shù)組并賦值給成員變量array,否則將Collection里面的元素全部取出來(lái)copy到一個(gè)新數(shù)組中,并且將該數(shù)組賦值給成員變量array。
public CopyOnWriteArrayList(Collection<? extends E> c) { Object[] elements; if (c.getClass() == CopyOnWriteArrayList.class) elements = ((CopyOnWriteArrayList<?>)c).getArray(); else { elements = c.toArray(); if (c.getClass() != ArrayList.class) elements = Arrays.copyOf(elements, elements.length, Object[].class); } setArray(elements); }
3.傳入一個(gè)數(shù)組的構(gòu)造方法
將傳入的數(shù)組的元素copy到一個(gè)新的Object數(shù)組,并且賦值給成員變量array。
public CopyOnWriteArrayList(E[] toCopyIn) { setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class)); }
接下來(lái)我們看核心的add(),remove(),get()方法。
- add(E e)
首先加鎖,然后通過(guò)Arrays.copyOf()方法將元素copy到一個(gè)新的數(shù)組中,新的數(shù)組的長(zhǎng)度為原數(shù)組的長(zhǎng)度+1,并且將需要加入的元素賦值到新數(shù)組的最后。最后將新數(shù)組賦值給成員變量array。
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(); } }
- add(int index, E element)
add(int index, E element)方法需要將元素加入到指定的索引位置中。首先也是先加鎖,保證線(xiàn)程安全,將原數(shù)組分為兩段進(jìn)行操作,根據(jù)index進(jìn)行分隔,分別copy index之前的元素和之后的元素,copy完成之后在將需要插入的元素設(shè)置到索引為index的位置上。之后將新數(shù)組賦值給成員變量array。
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(); } }
接下來(lái)看remove()方法。
- remove(int index)
remove(int index)方法需要在數(shù)組中移除指定索引的值。首先是加鎖,同樣也是將原數(shù)組分為兩段進(jìn)行操作,根據(jù)index進(jìn)行分隔,分別copy index之前的元素和之后的元素,copy到一個(gè)新數(shù)組中,新數(shù)組的長(zhǎng)度為原數(shù)組的長(zhǎng)度減一(注意這里是沒(méi)有copy index索引位置的值的,所以相當(dāng)于移除了index索引上的值)。之后將新數(shù)組賦值給成員變量array。
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) 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(); } }
接下來(lái)看get()方法
- get()
我們可以看到get()方法很簡(jiǎn)單,就是從array成員變量中取出對(duì)應(yīng)索引的值。并沒(méi)有加鎖處理。所以盡管是在并發(fā)高的情況下,get()方法的效率依舊是比較高的。
/** * {@inheritDoc} * * @throws IndexOutOfBoundsException {@inheritDoc} */ public E get(int index) { return get(getArray(), index); } private E get(Object[] a, int index) { return (E) a[index]; }
四:總結(jié)
CopyOnWriteArrayList為什么能夠保證線(xiàn)程安全,主要是因?yàn)橐韵聨c(diǎn):
1.在做修改操作的時(shí)候加鎖
2.每次修改都是將元素copy到一個(gè)新的數(shù)組中,并且將數(shù)組賦值到成員變量array中。
3.利用volatile關(guān)鍵字修飾成員變量array,這樣就可以保證array的引用的可見(jiàn)性,每次修改之前都能夠拿到最新的array引用。這點(diǎn)很關(guān)鍵。
看到這里,相信你已經(jīng)對(duì)CopyOnWriteArrayList非常了解了,CopyOnWriteArrayList在查詢(xún)多,修改操作少的情況下效率是非??捎^的,既能夠保證線(xiàn)程安全,又能有不錯(cuò)的效率。但是如果修改操作較多,就會(huì)導(dǎo)致數(shù)組頻繁的copy,效率就會(huì)有所下降,如果修改操作很多,那么直接使用Collections.synchronizedList(),或許也是一個(gè)不錯(cuò)的選擇。
以上就是詳解CopyOnWriteArrayList是如何保證線(xiàn)程安全的詳細(xì)內(nèi)容,更多關(guān)于CopyOnWriteArrayList 線(xiàn)程安全的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java NIO:淺析IO模型_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
在進(jìn)入Java NIO編程之前,我們今天先來(lái)討論一些比較基礎(chǔ)的知識(shí):I/O模型。對(duì)java io nio相關(guān)知識(shí)感興趣的朋友一起學(xué)習(xí)吧2017-05-05基于Properties實(shí)現(xiàn)配置數(shù)據(jù)庫(kù)驅(qū)動(dòng)
這篇文章主要介紹了基于Properties實(shí)現(xiàn)配置數(shù)據(jù)庫(kù)驅(qū)動(dòng),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-05-05java.util.NoSuchElementException原因及兩種解決方法
本文主要介紹了java.util.NoSuchElementException原因及兩種解決方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-06-06Java執(zhí)行SQL腳本文件到數(shù)據(jù)庫(kù)詳解
這篇文章主要為大家詳細(xì)介紹了Java執(zhí)行SQL腳本文件到數(shù)據(jù)庫(kù)的相關(guān)方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-06-06淺談Spring學(xué)習(xí)之request,session與globalSession作用域
這篇文章主要介紹了Spring學(xué)習(xí)之request,session與globalSession作用域的相關(guān)內(nèi)容,需要的朋友可以參考下。2017-09-09