Java中的線程安全集合CopyOnWriteArrayList解析
CopyOnWriteArrayList
CopyOnWriteArrayList是ArrayList的線程安全版本,從他的名字可以推測,CopyOnWriteArrayList是在有寫操作的時(shí)候會(huì)copy一份數(shù)據(jù),然后寫完再設(shè)置成新的數(shù)據(jù)。
CopyOnWriteArrayList適用于讀多寫少的并發(fā)場景,CopyOnWriteArraySet是線程安全版本的Set實(shí)現(xiàn),它的內(nèi)部通過一個(gè)CopyOnWriteArrayList來代理讀寫等操作,使得CopyOnWriteArraySet表現(xiàn)出了和CopyOnWriteArrayList一致的并發(fā)行為,他們的區(qū)別在于數(shù)據(jù)結(jié)構(gòu)模型的不同,set不允許多個(gè)相同的元素插入容器中,具體的細(xì)節(jié)將在下文中分析。
可以看到它實(shí)現(xiàn)了List接口,如果去看ArrayList的類圖的話,可以發(fā)現(xiàn)也是實(shí)現(xiàn)了List接口,也就得出一句廢話,ArrayList提供的api,CopyOnWriteArrayList也提供
下文中來分析CopyOnWriteArrayList是如何來做到線程安全的實(shí)現(xiàn)讀寫數(shù)據(jù)的,而且也會(huì)順便對(duì)比ArrayList的等效實(shí)現(xiàn)為什么不支持線程安全的。
下面首先展示了CopyOnWriteArrayList中比較重要的成員:
/** The lock protecting all mutators */ final transient ReentrantLock lock = new ReentrantLock(); /** The array, accessed only via getArray/setArray. */ private transient volatile Object[] array;
可以看到,CopyOnWriteArrayList使用了ReentrantLock來支持并發(fā)操作,array就是實(shí)際存放數(shù)據(jù)的數(shù)組對(duì)象。ReentrantLock是一種支持重入的獨(dú)占鎖,任意時(shí)刻只允許一個(gè)線程獲得鎖,所以可以安全的并發(fā)去寫數(shù)組,關(guān)于java中鎖的細(xì)節(jié),可以參考文章Java可重入鎖詳解。接下來看一下CopyOnWriteArrayList是如何使用這個(gè)lock來實(shí)現(xiàn)并發(fā)寫的,下面首先展示了add方法的代碼:
public boolean add(E e) { final ReentrantLock lock = this.lock; lock.lock(); //上鎖,只允許一個(gè)線程進(jìn)入 try { Object[] elements = getArray(); // 獲得當(dāng)前數(shù)組對(duì)象 int len = elements.length; Object[] newElements = Arrays.copyOf(elements, len + 1);//拷貝到一個(gè)新的數(shù)組中 newElements[len] = e;//插入數(shù)據(jù)元素 setArray(newElements);//將新的數(shù)組對(duì)象設(shè)置回去 return true; } finally { lock.unlock();//釋放鎖 } }
為了對(duì)比ArrayList,下面展示了ArrayList中的add方法的細(xì)節(jié):
public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; } private void ensureCapacityInternal(int minCapacity) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } ensureExplicitCapacity(minCapacity); } private void ensureExplicitCapacity(int minCapacity) { modCount++; // overflow-conscious code if (minCapacity - elementData.length > 0) grow(minCapacity); } private void grow(int minCapacity) { // overflow-conscious code int oldCapacity = elementData.length; int newCapacity = oldCapacity + (oldCapacity >> 1); if (newCapacity - minCapacity < 0) newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); // minCapacity is usually close to size, so this is a win: elementData = Arrays.copyOf(elementData, newCapacity); }
相比CopyOnWriteArrayList,ArrayList的add方法實(shí)現(xiàn)就顯得啰嗦的多,而且ArrayList并不支持線程安全,至于為什么不支持線程安全,看代碼就知道了,這幾個(gè)調(diào)用的方法中都沒有類似鎖(與鎖等效語義的組件)出現(xiàn)。下面再來看另一個(gè)版本的add方法:
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(); } }
在操作之前都是先lock住的,這里面有一個(gè)有意思的地方,因?yàn)樵摲椒梢灾付╥ndex來插入value,如果這個(gè)index位置上已經(jīng)有舊值,那么該方法的作用類似replace,如果該index為當(dāng)前數(shù)組的長度,那么該方法和上面分析的add方法等效,現(xiàn)在分析一下index位置上已經(jīng)有值的情況,會(huì)分為兩段copy,然后在中間設(shè)置新值?,F(xiàn)在來分析一下讀操作,下面是get方法的細(xì)節(jié):
public E get(int index) { return get(getArray(), index); } private E get(Object[] a, int index) { return (E) a[index]; }
可以發(fā)現(xiàn)是非常簡單的,而且讀是允許多個(gè)線程進(jìn)入的。下面來分析一下CopyOnWriteArrayList提高的迭代器。下面是兩個(gè)重要的變量:
/** Snapshot of the array */ private final Object[] snapshot; /** Index of element to be returned by subsequent call to next. */ private int cursor;
遍歷的時(shí)候首先會(huì)獲得當(dāng)前數(shù)組對(duì)象的一個(gè)拷貝,稱為快照,然后遍歷的操作會(huì)在該快照上進(jìn)行,那如果獲取了迭代器之后再對(duì)CopyOnWriteArrayList進(jìn)行寫操作會(huì)怎么樣?迭代器能感知到這種變化嗎?下面實(shí)際實(shí)驗(yàn)一下:
CopyOnWriteArrayList<String> copyOnWriteArrayList = new CopyOnWriteArrayList<>(); copyOnWriteArrayList.add("first"); copyOnWriteArrayList.add("second"); Iterator<String> iterator = copyOnWriteArrayList.iterator(); copyOnWriteArrayList.add("third"); while (iterator.hasNext()) { System.out.println(iterator.next()); } //output: first second
結(jié)果是不能感知,也就是說,這個(gè)快照并不會(huì)和外界有任何聯(lián)系,某個(gè)線程在獲取迭代器的時(shí)候就會(huì)拷貝一份,或者說,每一個(gè)線程都將獲得當(dāng)前時(shí)刻的一個(gè)快照,所以不需要加鎖就可以安全的實(shí)現(xiàn)遍歷,下面的代碼也證實(shí)了上面的說法:
public Iterator<E> iterator() { return new COWIterator<E>(getArray(), 0); }
CopyOnWriteArraySet
CopyOnWriteArraySet使用一個(gè)CopyOnWriteArrayList來做代理,它的所有api都是依賴于CopyOnWriteArrayList來實(shí)現(xiàn)的,下面的代碼也展示了這種代理的事實(shí): private final CopyOnWriteArrayList al;
/** * Creates an empty set. */ public CopyOnWriteArraySet() { al = new CopyOnWriteArrayList<E>(); }
下面來分析一下CopyOnWriteArraySet的寫操作實(shí)現(xiàn),比如add方法:
public boolean add(E e) { return al.addIfAbsent(e); } public boolean addIfAbsent(E e) { Object[] snapshot = getArray(); return indexOf(e, snapshot, 0, snapshot.length) >= 0 ? false : addIfAbsent(e, snapshot); } private boolean addIfAbsent(E e, Object[] snapshot) { final ReentrantLock lock = this.lock; lock.lock(); try { Object[] current = getArray(); int len = current.length; if (snapshot != current) { // Optimize for lost race to another addXXX operation int common = Math.min(snapshot.length, len); for (int i = 0; i < common; i++) if (current[i] != snapshot[i] && eq(e, current[i])) return false; if (indexOf(e, current, common, len) >= 0) return false; } Object[] newElements = Arrays.copyOf(current, len + 1); newElements[len] = e; setArray(newElements); return true; } finally { lock.unlock(); } }
set是一種不允許有重復(fù)元素的簡單數(shù)據(jù)結(jié)構(gòu),所以和CopyOnWriteArrayList不同,CopyOnWriteArraySet需要add在插入新元素的時(shí)候多做一些判斷,而CopyOnWriteArraySet在實(shí)現(xiàn)上使用了CopyOnWriteArrayList的addIfAbsent方法,這個(gè)方法的意思就是如果存在就不再插入,如果不存在再進(jìn)行插入。
本人分析了CopyOnWriteArrayList的實(shí)現(xiàn)細(xì)節(jié),并且分析了基于CopyOnWriteArrayList實(shí)現(xiàn)的CopyOnWriteArraySet,介于CopyOnWriteArrayList的簡單性,本文沒有太多亮點(diǎn),但是理解CopyOnWriteArrayList的實(shí)現(xiàn)細(xì)節(jié)是有必要的,在并發(fā)環(huán)境下,我們在選擇對(duì)象容器的時(shí)候需要考量是否需要選擇線程安全的容器,如果不需要,則優(yōu)先選擇ArrayList等沒有線程安全保障的容器,如果需要線程安全保障,那么必須選擇類似CopyOnWriteArrayList的線程安全的容器集合,否則會(huì)造成不可預(yù)料的錯(cuò)誤。當(dāng)然,實(shí)現(xiàn)線程安全的代價(jià)是以損失部分性能為代價(jià)的,畢竟有l(wèi)ock-unlock的操作,但是這又是必須的。接下來的文章會(huì)分析一些java中實(shí)現(xiàn)的線程安全的容器,比如ConcurrentHashMap等,當(dāng)然,也會(huì)對(duì)類似HashMap之類的非線程安全的容器集合進(jìn)行分析總結(jié),畢竟類似HashMap這樣的容器集合是我們經(jīng)常使用的,理解他的具體實(shí)現(xiàn)有助于我們更好的使用它。
到此這篇關(guān)于Java中的線程安全集合CopyOnWriteArrayList解析的文章就介紹到這了,更多相關(guān)Java的CopyOnWriteArrayList內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java微信公眾平臺(tái)開發(fā)(14) 微信web開發(fā)者工具使用
這篇文章主要為大家詳細(xì)介紹了Java微信公眾平臺(tái)開發(fā)第十四步,微信web開發(fā)者工具的使用方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-04-04詳解SpringBoot 創(chuàng)建定時(shí)任務(wù)(配合數(shù)據(jù)庫動(dòng)態(tài)執(zhí)行)
本篇文章主要介紹了SpringBoot 創(chuàng)建定時(shí)任務(wù)(配合數(shù)據(jù)庫動(dòng)態(tài)執(zhí)行),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-10-10Java設(shè)計(jì)模式之工廠模式分析【簡單工廠、工廠方法、抽象工廠】
這篇文章主要介紹了Java設(shè)計(jì)模式之工廠模式,結(jié)合實(shí)例形式分析了簡單工廠、工廠方法、抽象工廠等相關(guān)功能、實(shí)現(xiàn)與使用方法,需要的朋友可以參考下2018-04-04Java8通過CompletableFuture實(shí)現(xiàn)異步回調(diào)
這篇文章主要介紹了Java8通過CompletableFuture實(shí)現(xiàn)異步回調(diào),CompletableFuture是Java?8?中新增的一個(gè)類,它是對(duì)Future接口的擴(kuò)展,下文關(guān)于其更多相關(guān)詳細(xì)介紹需要的小伙伴可以參考一下2022-04-04Java Builder模式構(gòu)建MAP/LIST的實(shí)例講解
下面小編就為大家?guī)硪黄狫ava Builder模式構(gòu)建MAP/LIST的實(shí)例講解。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-10-10詳談Java泛型中T和問號(hào)(通配符)的區(qū)別
下面小編就為大家?guī)硪黄斦凧ava泛型中T和問號(hào)(通配符)的區(qū)別。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-10-10gradle使用maven-publish發(fā)布jar包上傳到私有maven配置
這篇文章主要介紹了gradle使用maven-publish發(fā)布jar包上傳到私有maven的配置示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-03-03