CopyOnWriteList簡介
ArrayList是線程不安全的,于是JDK在java.util.concurrent包下新增了一個線程并發(fā)安全的List-CopyOnWriteList,中心思想就是copy-on-write刃唐。簡單來說就是讀寫分離蟆肆,讀時共享唇礁,寫時復(fù)制(原本的array)更新(且為獨(dú)占式的加鎖)扫俺,而我們下面分析的源碼實(shí)現(xiàn)就是這個思想的體現(xiàn)彤枢。
成員屬性:
一個final鎖對象lock
使用volatile修飾的array,保證寫線程更新array之后別的線程能夠看到更新后的array,但是不能保證實(shí)時性:在數(shù)組副本上添加元素之后狰晚,還沒有更新array指向新地址之前,別的線程看到的還是舊的array缴啡。
后面一個是獲取數(shù)組壁晒,一個是設(shè)置數(shù)組。
構(gòu)造方法:
無參構(gòu)造方法就是創(chuàng)建一個新的長度為0的object數(shù)組业栅,然后調(diào)用setArray方法將其設(shè)置給CopyOnWriteList的成員變量array秒咐。
添加元素add(E e)?
修改元素set(int index,E element)
刪除元素remove(int index)
獲取元素get(i)
使用get(i)可以獲取指定位置i的元素
1 獲取array數(shù)組這里的get方法也體現(xiàn)了copy-on-write-list的弱一致性問題谬晕。
2 訪問傳入入?yún)⑾聵?biāo)的元素
我們看到get過程是沒有加鎖的,假設(shè)threadA執(zhí)行1之后2之前携取,這時恰好threadB執(zhí)行remove操作攒钳,threadB或獲取獨(dú)占鎖,然后執(zhí)行寫時復(fù)制操作歹茶,即復(fù)制一個新的數(shù)組newArray夕玩,然后在newArray中執(zhí)行刪除操作,更新array惊豺。如果在執(zhí)行setArray之前ThreadA搶到CPU時間片繼續(xù)執(zhí)行代碼燎孟,那么此時get還是從舊數(shù)組里面取得元素此時元素可能已經(jīng)被刪除了,所以這就是弱一致性問題尸昧。但卻保證了最終一致性揩页。
適用場景:
讀操作可以盡可能的快,而寫即使慢一些也沒關(guān)系烹俗,在很多 應(yīng)用場景中爆侣,讀操作可能會遠(yuǎn)遠(yuǎn)多于寫操作。黑名單是最典型的場景幢妄,假如我們有一個搜索網(wǎng)站兔仰,用戶在這個網(wǎng)站的搜索框中,輸入關(guān)鍵字搜索內(nèi)容蕉鸳,但是某些關(guān)鍵字不允許被搜索乎赴,這些不能被搜索的關(guān)鍵字會被放在一個黑名單中,黑名單并不需要實(shí)時更新潮尝,可能一段時間更新一次就行了榕吼,用戶每天多次搜索時,會檢查當(dāng)前關(guān)鍵字在不在黑名單中勉失,如果在羹蚣,則不能搜索,這種讀多寫少的場景也很適合使用CopyOnWrite集合乱凿。
讀寫規(guī)則:
讀寫鎖的思想是:讀讀共享顽素,寫寫互斥,讀寫互斥徒蟆,寫讀互斥戈抄,原因是由于讀操作不會修改原有的數(shù)據(jù),因此并發(fā)讀并不會有安全問題后专;而寫操作是不安全的,所以當(dāng)寫操作時输莺,不允許有讀操作加入戚哎,也不允許第二個寫線程加入裸诽。
為了將讀取的性能發(fā)揮到極致,CopyOnWriteArrayList讀取是完全不加鎖的型凳,寫入也不會阻塞讀取操作丈冬,也就是你可以在寫入的同時進(jìn)行讀取,只有寫入和寫入之間需要進(jìn)行同步甘畅,也就是不允許多個寫入同時發(fā)生埂蕊,但是在寫入時允許讀取同時發(fā)生,這樣一來疏唾,讀操作的性能就會大幅度提升蓄氧。
CopyOnWriteArrayList思想:
我們可以這樣打個比方,比如我在寫一篇簡書或者是修改一篇簡書文章槐脏,其實(shí)我正在修改只是沒有點(diǎn)擊保存重新發(fā)布喉童,所以別人看到的就是我之前沒有修改的那一篇(舊數(shù)據(jù))。只要這時我寫完了修改完了顿天。點(diǎn)擊保存(替換地址)堂氯,而這時你們看到的就是我新修改完后的簡書文章了。
因?yàn)闊o計(jì)其數(shù)的讀者可能正在讀我那一篇簡書文章牌废,他們之間肯定是不可以阻塞的咽白,假如阻塞的話,那么就要面臨排隊(duì)讀博客了鸟缕。而且還要保證我修改簡書文章的時候晶框,讀者還能讀到簡書內(nèi)容,這時候就需要讀寫分離(讀與寫同時并發(fā)叁扫,互不影響)三妈,即達(dá)到寫有鎖,讀無鎖莫绣,讀寫直接不阻塞的效果畴蒲。
從CopyOnWriteArrayList的名字就可以看出來意思就是,當(dāng)容器需要被修改的時候对室,不直接修改當(dāng)前容器模燥,而是先將當(dāng)前容器進(jìn)行copy,復(fù)制出一個新的容器掩宜,然后修改新的容器蔫骂,完成修改之后,再將原容器的引用指向新的容器牺汤,這樣就完成了整個修改過程辽旋。
這樣做的好處就是,它利用了 不變性 原理,因?yàn)槿萜髅看涡薷亩际莿?chuàng)建新副本补胚,所以對于舊容器來說码耐,其實(shí)是不可變的,也是線程安全的溶其,無需進(jìn)一步的同步操作骚腥。我們也可以并發(fā)地讀,而不需要加鎖瓶逃,因?yàn)楫?dāng)前容器不會添加任何元素束铭,也不會有修改。
CopyOnWriteArrayList的所有操作(add,set,remove等)都是通過創(chuàng)建底層數(shù)組的新副本來實(shí)現(xiàn)的厢绝,所以CopyOnWrite容器也是一種讀寫分離的思想體現(xiàn)契沫,讀和寫使用不同的容器。
缺點(diǎn):
因?yàn)槭褂昧藢憰r復(fù)制機(jī)制代芜,所以在進(jìn)行寫操作的時候埠褪,內(nèi)存里會同時駐扎兩個對象的內(nèi)存,這一點(diǎn)會占用額外的內(nèi)存空間挤庇。
在元素較多或復(fù)雜的情況下钞速,復(fù)制的開銷也很大。
復(fù)制過程不僅會占用多的內(nèi)存嫡秕,還會消耗CPU資源渴语,會降低整體性能。
數(shù)據(jù)一致性問題:
由于CopyOnWrite容器的修改是先修改副本昆咽,所以這次修改對于其他線程來說驾凶,并不是實(shí)時能看到的,只有在修改完成之后才能體現(xiàn)出來掷酗。如果你希望寫入的數(shù)據(jù)馬上被其他線程看到的話调违,CopyOnWrite容器不適合你。