高并发第八弹:J.U.C起航(java.util.concurrent)
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); //復制當前數組并且擴容+1newElements[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)包含了全部的數據;層次越高,跳躍性越大,包含的數據越少。
跳表包含一個表頭,它查找數據時,是從上往下,從左往右進行查找?,F在“需要找出值為32的節點”為例,來對比說明跳表和普遍的鏈表。
情況1:鏈表中查找“32”節點
路徑如下圖1-02所示:
需要4步(紅色部分表示路徑)。
?
情況2:跳表中查找“32”節點
路徑如下圖1-03所示:
忽略索引垂直線路上路徑的情況下,只需要2步(紅色部分表示路徑)。
先以數據“7,14,21,32,37,71,85”序列為例,來對跳表進行簡單說明。
跳表分為許多層(level),每一層都可以看作是數據的索引,這些索引的意義就是加快跳表查找數據速度。每一層的數據都是有序的,上一層數據是下一層數據的子集,并且第一層(level 1)包含了全部的數據;層次越高,跳躍性越大,包含的數據越少。
跳表包含一個表頭,它查找數據時,是從上往下,從左往右進行查找?,F在“需要找出值為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帝鄉,在沒有額外同步的情況下,可以被多個線程并發訪問,但是任何線程都不能修改它
- 線程安全對象:一個線程安全的對象或者容器,在內部通過同步機制來保障線程安全,多以其他線程無需額外的同步就可以通過公共接口隨意訪問他
- 被守護對象:被守護對象只能通過獲取特定的鎖來訪問。
?
不好意思 虎頭蛇尾了.實在扛不住了
轉載于:https://www.cnblogs.com/aihuxi/p/9683805.html
總結
以上是生活随笔為你收集整理的高并发第八弹:J.U.C起航(java.util.concurrent)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: POJ 3694 (tarjan缩点+L
- 下一篇: C#接口