1. 程式人生 > >高並發第八彈:J.U.C起航(java.util.concurrent)

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

思想 依賴 分享圖片 變量 ... 查找 沖突 dex http

java.util.concurrent是JDK自帶的一個並發的包主要分為以下5部分:

  • 並發工具類(tools)
  • 顯示鎖(locks)
  • 原子變量類(aotmic)
  • 並發集合(collections)
  • Executor線程執行器

我們今天就說說 並發集合,除開 Queue,放在線程池的時候講

技術分享圖片

先介紹以下 CopyOnWrite:

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

因為 網友總結的優缺點是:

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

不好意思 虎頭蛇尾了.實在扛不住了

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