今天我們再來研究一個(gè)List-CopyOnWriteArrayList.
我們首先從類注釋來大致了解一下這個(gè)類:
從中我們可以提取出來幾點(diǎn)關(guān)鍵點(diǎn):
- 它先當(dāng)于一個(gè)線程安全的ArrayList
- 向其中添加數(shù)據(jù)或者刪除數(shù)據(jù)的操作洪规,都是線程安全的
- 它在添加數(shù)據(jù)或者刪除數(shù)據(jù)方面效率有點(diǎn)低
- Iterator不會(huì)反應(yīng)出新增加的元素
在接下來的文章中,我們將會(huì)回答以下幾個(gè)問題:
- 它是如何實(shí)現(xiàn)的?
- 為什么它是線程安全的
- 為什么在增加數(shù)據(jù)時(shí)松蒜,它效率很低?
- 它跟ArrayList有什么異同?
- 它適應(yīng)于什么場景?
解答
它是如何實(shí)現(xiàn)的?
它的實(shí)現(xiàn)方式擂涛,跟ArrayList基本上一致读串,它的底層數(shù)據(jù)結(jié)構(gòu),也是一個(gè)數(shù)組歼指,區(qū)別就是爹土,增加了一個(gè)鎖來保證線程安全.
為什么它是線程安全的?
CopyOnWriteArrayList增加了一個(gè)鎖,一個(gè)可重入鎖ReentrantLock踩身,就是在進(jìn)行寫操作時(shí)胀茵,通過這個(gè)鎖來保證線程安全性.
另外,在執(zhí)行寫操作的時(shí)候挟阻,會(huì)創(chuàng)建一個(gè)新的數(shù)組琼娘,這個(gè)數(shù)組是原數(shù)組的拷貝,然后它會(huì)對(duì)這個(gè)新數(shù)組執(zhí)行寫操作附鸽,在寫操作完成之后脱拼,會(huì)將原先的指向舊數(shù)組的引用,讓其重新指向新數(shù)組.這就保證了用戶在更新數(shù)據(jù)的過程中坷备,不會(huì)看到臟數(shù)據(jù).
我們看一下set(int index, E element)的實(shí)現(xiàn)熄浓,就能明白上面所述了.
為什么在增加數(shù)據(jù)時(shí),它效率很低?
我們先貼出add(E e)方法的源碼:
這里我們可以看到省撑,當(dāng)新增數(shù)據(jù)的時(shí)候赌蔑,會(huì)創(chuàng)建一個(gè)長度為原數(shù)組長度+1的新數(shù)組,將原數(shù)組中的數(shù)據(jù)拷貝過去竟秫,然后將新數(shù)組的最后一位設(shè)置為想要添加的數(shù)據(jù).
對(duì)比ArrayList的實(shí)現(xiàn)娃惯,我們可以發(fā)現(xiàn),這里由于頻繁的創(chuàng)建新數(shù)組肥败,移動(dòng)元素趾浅,便會(huì)造成效率低.
它跟ArrayList有什么異同?
相同點(diǎn)是,底層數(shù)據(jù)結(jié)構(gòu)都是數(shù)組.
不同點(diǎn)是馒稍,它引入了一個(gè)可重入鎖來保證線程安全皿哨,另外,它不跟ArrayList一樣纽谒,在增加數(shù)據(jù)時(shí)证膨,如果容量不夠了,就按照一個(gè)公式進(jìn)行擴(kuò)容佛舱,而是每次增加數(shù)據(jù)時(shí)椎例,如果容量不夠了挨决,只擴(kuò)容一位.
另外,它不是Fail-Fast的订歪,即脖祈,在使用Iterator進(jìn)行迭代時(shí),如果底層數(shù)組發(fā)生了變化刷晋,不會(huì)拋出ConcurrentModificationException.
它適用于什么場景?
由于其在修改數(shù)據(jù)時(shí)盖高,效率偏低,所以眼虱,它主要適用于讀多寫少喻奥,并且數(shù)據(jù)容器需要是線程安全的場景.
總結(jié)
其實(shí),Java中很多源碼的實(shí)現(xiàn)都是差不多的.就拿我們現(xiàn)在介紹的集合框架來說吧捏悬,幾個(gè)List的實(shí)現(xiàn)基本上都差不多撞蚕,最多就是底層的數(shù)據(jù)結(jié)構(gòu)換了一下,而底層數(shù)據(jù)結(jié)構(gòu)其實(shí)就那么多过牙,數(shù)組甥厦,鏈表,樹寇钉,雖然鏈表和樹又可以細(xì)分為很多種類刀疙,但是,整體來說扫倡,整個(gè)集合框架谦秧,同類的集合,還是大同小異的.
另外撵溃,同樣是集合框架中的一部分疚鲤,有的集合框架就依賴于其他的集合框架,比如Set的實(shí)現(xiàn)征懈,基本上就是依賴于對(duì)應(yīng)的Map的實(shí)現(xiàn).
因?yàn)樗鼈兓旧隙际窍嗤氖В晕覀冏x起來會(huì)很簡單.但是揩悄,我們在讀源碼時(shí)卖哎,除了了解其實(shí)現(xiàn)原理,還要有其他的一系列目標(biāo)删性,比如亏娜,對(duì)比各個(gè)集合框架之間的異同,其各個(gè)操作的時(shí)間復(fù)雜度蹬挺,適合在什么場景使用.
讀源碼時(shí)维贺,除了其大體輪廓,很多時(shí)候巴帮,我們還要注意其實(shí)現(xiàn)的細(xì)節(jié)溯泣,因?yàn)楹芏嗤愒创a虐秋,在大體輪廓上都是相同的,只有在實(shí)現(xiàn)細(xì)節(jié)上有差異.注意與其他同類源碼對(duì)比垃沦,找出其最適合的場景.