這一篇系集合list類最後一篇,此次咱們來一塊兒學習一下CopyOnWriteArrayList。html
老規矩,先簡單介紹一下。在源碼中,開頭註釋是這麼寫的,ArrayList的一個線程安全的變種,其中全部的可變操做包括add、set等都是經過底層數組的副本實現的。數組
Copy-On-Write,在寫入時複製,這個就說明了這個類的工做方式,在進行可變操做好比add、set等操做時先複製一份源的副本,對副本進行操做,而後再將源列表的引用指向副本。安全
這麼作其實成本很高,可是當改變的操做(add、set、remove等)遠遠少於遍歷的操做的時候,這個CopyOnWriteArrayList可能更有效率。而且在不能或不想對遍歷加鎖,可是想要排除併發線程的干擾時也是很是有用的。快照方式的iterator用了一個指向這個iterator被建立時數組的狀態的引用。這個狀態的引用在iterator的聲明週期中永遠也不會改變,因此干擾時不可能的,而且iterator保證不會拋出ConcurrentModificationException。迭代器(iterator)建立之後,對原列表的增長、刪除或者改變都不會反映到這個iterator上。iterator即這個迭代器不支持remove、set、add等改變元素的操做,調用這些方法會拋出UnsupportedOperationException。多線程
支持全部類型的元素,包括null。併發
內存一致性效果:同其餘併發集合同樣,將對象放入CopyOnWriteArrayList以前的線程的操做,先於另外一個線程從CopyOnWriteArrayList中刪除或者訪問元素後的操做發生。學習
咱們也來首先看一下內部存儲結構: 線程
接下來咱們根據add這個方法來看一下這個類的主要實現方式,cdn
這麼一看其實很簡單,在添加元素以前首先得到鎖,之因此加鎖,是爲了多線程環境下,防止複製出多個副原本。以後使用getArray()方法去得到本來的元素列表,而後根據Array.copyOf方法區根據須要去複製一個新的數組,這裏是將新數組的長度加一。以後對這個新數組進行操做。 最後用setArray方法將這個CopyOnWriteArrayList內部存儲數組指向這個新數組。而後使用lock.unlock()釋放鎖。咱們來看一下getArray()方法與setArray方法。htm
沒啥就是一個get與set,可是是final的即不可被重寫。對象
CopyOnWrite容器即寫時複製的容器。通俗的理解是當咱們往一個容器添加元素的時候,不直接往當前容器添加,而是先將當前容器進行Copy,複製出一個新的容器,而後新的容器裏添加元素,添加完元素以後,再將原容器的引用指向新的容器。這樣作的好處是咱們能夠對CopyOnWrite容器進行併發的讀,而不須要加鎖,由於當前容器不會添加任何元素。因此CopyOnWrite容器也是一種讀寫分離的思想,讀和寫不一樣的容器。
CopyOnWrite容器有不少優勢,可是同時也存在兩個問題,即內存佔用問題和數據一致性問題。因此在開發的時候須要注意一下。
**內存佔用問題。**由於CopyOnWrite的寫時複製機制,因此在進行寫操做的時候,內存裏會同時駐紮兩個對象的內存,舊的對象和新寫入的對象(注意:在複製的時候只是複製容器裏的引用,只是在寫的時候會建立新對象添加到新容器裏,而舊容器的對象還在使用,因此有兩份對象內存)。若是這些對象佔用的內存比較大,好比說200M左右,那麼再寫入100M數據進去,內存就會佔用300M,那麼這個時候頗有可能形成頻繁的Yong GC和Full GC。
針對內存佔用問題,能夠經過壓縮容器中的元素的方法來減小大對象的內存消耗,好比,若是元素全是10進制的數字,能夠考慮把它壓縮成36進制或64進制。或者不使用CopyOnWrite容器,而使用其餘的併發容器,如ConcurrentHashMap。
**數據一致性問題。**CopyOnWrite容器只能保證數據的最終一致性,不能保證數據的實時一致性。因此若是你但願寫入的的數據,立刻能讀到,請不要使用CopyOnWrite容器。