java.util.concurrent是JDK自帶的一個併發的包主要分爲如下5部分:html
- 併發工具類(tools)
- 顯示鎖(locks)
- 原子變量類(aotmic)
- 併發集合(collections)
- Executor線程執行器
咱們今天就說說 併發集合,除開 Queue,放在線程池的時候講java
先介紹如下 CopyOnWrite:node
Copy-On-Write簡稱COW,是一種用於程序設計中的優化策略。其基本思路是,從一開始你們都在共享同一個內容,當某我的想要修改這個內容的時候,纔會真正把內容Copy出去造成一個新的內容而後再改,這是一種延時懶惰策略。從JDK1.5開始Java併發包裏提供了兩個使用CopyOnWrite機制實現的併發容器,它們是CopyOnWriteArrayList和CopyOnWriteArraySet。CopyOnWrite容器很是有用,能夠在很是多的併發場景中使用到 .算法
CopyOnWrite容器即寫時複製的容器。通俗的理解是當咱們往一個容器添加元素的時候,不直接往當前容器添加,而是先將當前容器進行Copy,複製出一個新的容器,而後新的容器裏添加元素,添加完元素以後,再將原容器的引用指向新的容器。這樣作的好處是咱們能夠對CopyOnWrite容器進行併發的讀,而不須要加鎖,由於當前容器不會添加任何元素。因此CopyOnWrite容器也是一種讀寫分離的思想,讀和寫不一樣的容器。數組
public class CopyOnWriteArrayList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable { private static final long serialVersionUID = 8673264195747942595L; /** The lock protecting all mutators */ final transient ReentrantLock lock = new ReentrantLock(); /** The array, accessed only via getArray/setArray. */ private transient volatile Object[] array; ............................
public boolean add(E e) { final ReentrantLock lock = this.lock; lock.lock(); try { Object[] elements = getArray();//獲取當前數組數據 int len = elements.length; Object[] newElements = Arrays.copyOf(elements, len + 1); //複製當前數組而且擴容+1 newElements[len] = e; setArray(newElements);//將原來的數組指向新的數組 return true; } finally { lock.unlock(); } }
下面這篇文章驗證了CopyOnWriteArrayList和同步容器的性能:安全
http://blog.csdn.net/wind5shy/article/details/5396887數據結構
下面這篇文章簡單描述了CopyOnWriteArrayList的使用:多線程
http://blog.csdn.net/imzoer/article/details/9751591併發
由於 網友總結的優缺點是:dom
-
缺點:
1.寫操做時複製消耗內存,若是元素比較多時候,容易致使young gc 和full gc。
2.不能用於實時讀的場景.因爲複製和add操做等須要時間,故讀取時可能讀到舊值。
能作到最終一致性,但沒法知足實時性的要求,更適合讀多寫少的場景。
若是沒法知道數組有多大,或者add,set操做有多少,慎用此類,在大量的複製副本的過程當中很容易出錯。 -
設計思想:
1.讀寫分離
2.最終一致性
3.使用時另外開闢空間,防止併發衝突
這個還真是主要是針對 讀多的條件.畢竟寫一個就要開闢一個空間.太耗資源了.其實仍是建議用手動的方式來控制集合的併發.
1. ArrayList –> CopyOnWriteArrayList
它至關於線程安全的ArrayList。和ArrayList同樣,它是個可變數組;可是和ArrayList不一樣的時,它具備如下特性:
1. 它最適合於具備如下特徵的應用程序:List 大小一般保持很小,只讀操做遠多於可變操做,須要在遍歷期間防止線程間的衝突。
2. 它是線程安全的。
3. 由於一般須要複製整個基礎數組,因此可變操做(add()、set() 和 remove() 等等)的開銷很大。
4. 迭代器支持hasNext(), next()等不可變操做,但不支持可變 remove()等操做。
5. 使用迭代器進行遍歷的速度很快,而且不會與其餘線程發生衝突。在構造迭代器時,迭代器依賴於不變的數組快照。
2. HashSet –> CopyOnWriteArraySet
它是線程安全的無序的集合,能夠將它理解成線程安全的HashSet。有意思的是,CopyOnWriteArraySet和HashSet雖然都繼承於共同的父類AbstractSet;可是,HashSet是經過「散列表(HashMap)」實現的,而CopyOnWriteArraySet則是經過「動態數組(CopyOnWriteArrayList)」實現的,並非散列表。
和CopyOnWriteArrayList相似,CopyOnWriteArraySet具備如下特性:
1. 它最適合於具備如下特徵的應用程序:Set 大小一般保持很小,只讀操做遠多於可變操做,須要在遍歷期間防止線程間的衝突。
2. 它是線程安全的。
3. 由於一般須要複製整個基礎數組,因此可變操做(add()、set() 和 remove() 等等)的開銷很大。
4. 迭代器支持hasNext(), next()等不可變操做,但不支持可變 remove()等 操做。
5. 使用迭代器進行遍歷的速度很快,而且不會與其餘線程發生衝突。在構造迭代器時,迭代器依賴於不變的數組快照。
SkipList 跳錶:先介紹這個吧
介紹的很詳細 https://blog.csdn.net/sunxianghuang/article/details/52221913
更優秀的 :https://www.cnblogs.com/skywang12345/p/3498556.html
總結起來就是:
傳統意義的單鏈表是一個線性結構,向有序的鏈表中插入一個節點須要O(n)的時間,查找操做須要O(n)的時間
跳錶查找的複雜度爲O(n/2)。跳躍表其實也是一種經過「空間來換取時間」的一個算法,經過在每一個節點中增長了向前的指針,從而提高查找的效率。
先以數據「7,14,21,32,37,71,85」序列爲例,來對跳錶進行簡單說明。
跳錶分爲許多層(level),每一層均可以看做是數據的索引,這些索引的意義就是加快跳錶查找數據速度。每一層的數據都是有序的,上一層數據是下一層數據的子集,而且第一層(level 1)包含了所有的數據;層次越高,跳躍性越大,包含的數據越少。
跳錶包含一個表頭,它查找數據時,是從上往下,從左往右進行查找。如今「須要找出值爲32的節點」爲例,來對比說明跳錶和廣泛的鏈表。
狀況1:鏈表中查找「32」節點
路徑以下圖1-02所示:
須要4步(紅色部分表示路徑)。
狀況2:跳錶中查找「32」節點
路徑以下圖1-03所示:
忽略索引垂直線路上路徑的狀況下,只須要2步(紅色部分表示路徑)。
先以數據「7,14,21,32,37,71,85」序列爲例,來對跳錶進行簡單說明。
跳錶分爲許多層(level),每一層均可以看做是數據的索引,這些索引的意義就是加快跳錶查找數據速度。每一層的數據都是有序的,上一層數據是下一層數據的子集,而且第一層(level 1)包含了所有的數據;層次越高,跳躍性越大,包含的數據越少。
跳錶包含一個表頭,它查找數據時,是從上往下,從左往右進行查找。如今「須要找出值爲32的節點」爲例,來對比說明跳錶和廣泛的鏈表。
狀況1:鏈表中查找「32」節點
路徑以下圖1-02所示:
須要4步(紅色部分表示路徑)。
狀況2:跳錶中查找「32」節點
路徑以下圖1-03所示:
忽略索引垂直線路上路徑的狀況下,只須要2步(紅色部分表示路徑)。
3. TreeMap –> ConcurrentSkipListMap
下面說說Java中ConcurrentSkipListMap的數據結構。
(01) ConcurrentSkipListMap繼承於AbstractMap類,也就意味着它是一個哈希表。
(02) Index是ConcurrentSkipListMap的內部類,它與「跳錶中的索引相對應」。HeadIndex繼承於Index,ConcurrentSkipListMap中含有一個HeadIndex的對象head,head是「跳錶的表頭」。
(03) Index是跳錶中的索引,它包含「右索引的指針(right)」,「下索引的指針(down)」和「哈希表節點node」。node是Node的對象,Node也是ConcurrentSkipListMap中的內部類。
/** * Special value used to identify base-level header */ private static final Object BASE_HEADER = new Object(); /** * 跳錶的最頂層索引 */ private transient volatile HeadIndex<K,V> head; /** * * 比較器用於維護此映射中的順序,或者若是使用天然排序,則爲空。(非私有的,以 * 簡化嵌套類中的訪問)。 * */ final Comparator<? super K> comparator; /** Lazily initialized key set */ //懶惰初始化密鑰集 private transient KeySet<K> keySet; /** Lazily initialized entry set */ private transient EntrySet<K,V> entrySet; /** Lazily initialized values collection */ private transient Values<V> values; /** Lazily initialized descending key set */
源碼我也沒精力去詳勘了.就總結一下
4. TreeSet –> ConcurrentSkipListSet
(01) ConcurrentSkipListSet繼承於AbstractSet。所以,它本質上是一個集合。
(02) ConcurrentSkipListSet實現了NavigableSet接口。所以,ConcurrentSkipListSet是一個有序的集合。
(03) ConcurrentSkipListSet是經過ConcurrentSkipListMap實現的。它包含一個ConcurrentNavigableMap對象m,而m對象其實是ConcurrentNavigableMap的實現類ConcurrentSkipListMap的實例。ConcurrentSkipListMap中的元素是key-value鍵值對;而ConcurrentSkipListSet是集合,它只用到了ConcurrentSkipListMap中的key!
(4)同其餘set集合,是基於map集合的(基於ConcurrentSkipListMap),在多線程環境下,裏面的contains、add、remove操做都是線程安全的。
(5)多個線程能夠安全的併發的執行插入、移除、和訪問操做。可是對於批量操做addAll、removeAll、retainAll和containsAll並不能保證以原子方式執行,緣由是addAll、removeAll、retainAll底層調用的仍是 contains、add、remove方法,只能保證每一次的執行是原子性的,表明在單一執行操縱時不會被打斷,可是不能保證每一次批量操做都不會被打斷。在使用批量操做時,仍是須要手動加上同步操做的。
(6)不容許使用null元素的,它沒法可靠的將參數及返回值與不存在的元素區分開來。
5. HashMap –> ConcurrentHashMap
- 不容許空值,在實際的應用中除了少數的插入操做和刪除操做外,絕大多數咱們使用map都是讀取操做。並且讀操做大多數都是成功的。基於這個前提,它針對讀操做作了大量的優化。所以這個類在高併發環境下有特別好的表現。
- ConcurrentHashMap做爲Concurrent一族,其有着高效地併發操做,相比Hashtable的笨重,ConcurrentHashMap則更勝一籌了。
- 在1.8版本之前,ConcurrentHashMap採用分段鎖的概念,使鎖更加細化,可是1.8已經改變了這種思路,而是利用CAS+Synchronized來保證併發更新的安全,固然底層採用數組+鏈表+紅黑樹的存儲結構。
- 源碼分析:推薦參考chenssy的博文:J.U.C之Java併發容器:ConcurrentHashMap
安全共享對象策略
- 線程限制:一個被線程限制的對象,由線程獨佔,而且只能被佔有它的線程修改
- 共享只讀:一個共享只讀的U帝鄉,在沒有額外同步的狀況下,能夠被多個線程併發訪問,可是任何線程都不能修改它
- 線程安全對象:一個線程安全的對象或者容器,在內部經過同步機制來保障線程安全,多以其餘線程無需額外的同步就能夠經過公共接口隨意訪問他
- 被守護對象:被守護對象只能經過獲取特定的鎖來訪問。
很差意思 有始無終了.實在扛不住了