二. 集合學習之CopyOnWriteArrayList
本博文主要記錄CopyOnWriteArrayList的使用,通過對ArrayList的對比了解CopyOnWriteArrayList具有什麽樣的特性,並了解其內部實現原理。
在你的應用中有一個列表(List),它被頻繁的遍歷,但是很少被修改。像“你的主頁上的前十個分類,它被頻繁的訪問,但是每個小時通過Job來調度更新”。
如果你使用ArrayList來作為該列表的數據結構並且不使用同步(synchronization),你可能會遇到ConcurrentModificationException,因為在你使用Job修改該列表時,其他的代碼可能正在遍歷該列表。
有些開發人員可能使用Vector或Collections.synchronizedList(List<T>)的方式來解決該問題。但是這並沒有效果!雖然在列表上add(),remove()和get()方法現在對線程是安全的,但遍歷時仍然會拋出ConcurrentModificationException!在你遍歷在列表時,你需要在該列表上使用同步,同時,在修改它時,也需要使用同步機制。這對性能和可擴展性來說是一個噩夢。同步需要在所有的地方出現,僅僅是因為每個小時都需要做更新。
幸運的是,這裏有更好的解決方案。使用CopyOnWriteArrayList。
CopyOnWriteArrayList是jdk concurrent包中提供的一個非阻塞型的,線程安全的List實現。
CopyOnWriteArrayList在進行數據修改時,都不會對數據進行鎖定,每次修改時,先拷貝整個數組,然後修改其中的一些元素,完成上述操作後,替換整個數組的指針。 對CopyOnWriteArrayList進行讀取時,也不進行數據鎖定,直接返回需要查詢的數據,如果需要返回整個數組,那麽會將整個數組拷貝一份,再返回,保證內部array在任何情況下都是只讀的。在CopyOnWriteArrayList裏處理寫操作(包括add、remove、set等)是先將原始的數據通過JDK1.6的Arrays.copyof()來生成一份新的數組
然後在新的數據對象上進行寫,寫完後再將原來的引用指向到當前這個數據對象,這樣保證了每次寫都是在新的對象上(因為要保證寫的一致性,這裏要對各種寫操作要加一把鎖,JDK1.6在這裏用了重入鎖),
然後讀的時候就是在引用的當前對象上進行讀(包括get,iterator等),不存在加鎖和阻塞,針對iterator使用了一個叫 COWIterator的閹割版叠代器,因為不支持寫操作,當獲取CopyOnWriteArrayList的叠代器時,是將叠代器裏的數據引用指向當前引用指向的數據對象,無論未來發生什麽寫操作,都不會再更改叠代器裏的數據對象引用,所以叠代器也很安全。
CopyOnWriteArrayList中寫操作需要大面積復制數組,所以性能肯定很差,但是讀操作因為操作的對象和寫操作不是同一個對象,讀之間也不需要加鎖,讀和寫之間的同步處理只是在寫完後通過一個簡單的“=”將引用指向新的數組對象上來,這個幾乎不需要時間,這樣讀操作就很快很安全,適合在多線程裏使用,絕對不會發生ConcurrentModificationException ,所以最後得出結論:CopyOnWriteArrayList適合使用在讀操作遠遠大於寫操作的場景裏,比如緩存。
正因為上述讀寫特性,如果需要頻繁對CopyOnWriteArrayList進行修改,而很少讀取的話,那麽會嚴重降低系統性能。 因為沒有鎖的幹預,所以CopyOnWriteArrayLIst在少量修改,頻繁讀取的場景下,有很好的並發性能。
二. 集合學習之CopyOnWriteArrayList