高併發第八彈:J.U.C起航(java.util.concurrent)

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帝鄉,在沒有額外同步的狀況下,能夠被多個線程併發訪問,可是任何線程都不能修改它
  • 線程安全對象:一個線程安全的對象或者容器,在內部經過同步機制來保障線程安全,多以其餘線程無需額外的同步就能夠經過公共接口隨意訪問他
  • 被守護對象:被守護對象只能經過獲取特定的鎖來訪問。

 

很差意思 有始無終了.實在扛不住了

相關文章
相關標籤/搜索