Java中的拷貝數(shù)組CopyOnWriteArrayList詳解
CopyOnWriteArrayList詳解
ArrayList和LinkedList都不是線程安全的,如果需要線程安全的List,可以使用Collections.synchronizedList來生成一個同步list,但是這個同步list的方法都是通過synchronized修飾來保證同步的,并發(fā)性能不高。那么如何提高并發(fā)性能呢?比如某些場景下,對List的讀操作遠多于寫操作,那么CopyOnWriteArrayList就派上用場了。
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是用來在寫操作時加鎖使用的,具體使用下面方法部分再看。
數(shù)組array是CopyOnWriteArrayList的核心部分了,所有的元素都存放在這個數(shù)組中。為了保證可見性,所以被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)); }
三個構造函數(shù)都是為了給array數(shù)組賦值,生成初始數(shù)組。
下面看下它的幾個關鍵方法: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比較簡單,就是直接取數(shù)組索引處的元素。注意get方法沒有加鎖。
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方法,上來就先加鎖,然后取出原數(shù)組后拷貝生成了一個新的數(shù)組,注意,此時原有的array數(shù)組沒有變,get訪問時還是跟之前一樣。當把新的數(shù)組替換掉array后,由于是volatile修飾的,get訪問時就會訪問添加過元素的新數(shù)組。這樣就保證了讀寫同時進行時,讀不需要加鎖依然不會有并發(fā)問題。最后釋放鎖后,別的寫操作獲得鎖,再次進行替換操作,這樣保證寫操作與寫操作之間不會有并發(fā)問題。
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ù)組進行替換。
3、總結
- 所有的元素均存儲到數(shù)組中
- 寫操作(add/set/remove等)會改變數(shù)組結構,采用新生成一個數(shù)組然后進行替換的做法,保證了在此期間原數(shù)組可以正常訪問。同時操作之前需要先獲取鎖,避免寫操作之間產生并發(fā)問題。
- 讀操作由于不需要改變數(shù)組結構,且寫操作時,對原有的數(shù)組不進行修改,此時仍可正常讀取。寫操作將新數(shù)組進行替換后,由于數(shù)組被volatile修飾,保證了可見性,此時也可正正常讀取。所以讀操作不需要加鎖。
- 因為每次寫操作都會帶來數(shù)組拷貝,所以當讀操作遠大于寫操作時,才可考慮使用此容器。
到此這篇關于Java中的拷貝數(shù)組CopyOnWriteArrayList詳解的文章就介紹到這了,更多相關CopyOnWriteArrayList詳解內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
SpringBoot整合redis中的JSON序列化文件夾操作小結
在我們日常的項目開發(fā)中,使用redis作為緩存,來提高系統(tǒng)訪問速度和緩解系統(tǒng)壓力,在使用中遇到幾個問題,本文給大家詳細總結下,對SpringBoot整合redis?JSON序列化相關知識感興趣的朋友一起看看吧2022-02-02eclipse創(chuàng)建一個基于maven的web項目詳細步驟
開始學習maven,并用maven創(chuàng)建了第一個屬于自己的web項目,下面這篇文章主要給大家介紹了關于eclipse創(chuàng)建一個基于maven的web項目的相關資料,文中通過圖文介紹的非常詳細,需要的朋友可以參考下2023-12-12IDEA中Maven依賴包無法下載或導入的解決方案(系統(tǒng)缺失文件導致)
在配置Maven環(huán)境時,可能會遇到各種報錯問題,首先確保Maven路徑配置正確,例如使用apache-maven-3.5.0版本,則需要在系統(tǒng)環(huán)境變量的Path中添加其bin目錄路徑,并上移優(yōu)先級,接下來,在Maven的conf目錄下修改settings.xml文件,將鏡像源改為阿里云2024-09-09