android ConcurrentHashMap的使用
今天,簡單講講ConcurrentHashMap使用。
一.ConcurrentHashMap類的理解
ConcurrentHashMap是Java?中支持高并發,高吞吐量的hashMap實現。ConcurrnetHashMap是基于線程安全的一個類。曾看到某個帖子http://blog.sina.com.cn/s/blog_605f5b4f0100qsio.html,在針對于Collections.synchronizedMap、ConcurrentHashMap、Hashtable,進行性能測試。模擬1000個并發,每個測試1000次操作,循環測試100輪,最后讀寫對應的時間分別為:(6544ms,707ms),(5960ms,650ms),(6719ms,713ms)(此數據依據當前運行條件,有浮動,但是得出的結論是,ConcurrentHashMap比其余兩個在讀和取上都會快一些)。
?集合是編程中最常用的數據結構。而談到并發,幾乎總是離不開集合這類高級數據結構的支持。比如兩個線程需要同時訪問一個中間臨界區(Queue),比如常會用緩存作為外部文件的副本(HashMap)。這篇文章主要分析jdk1.5的3種并發集合類型(concurrent,copyonright,queue)中的ConcurrentHashMap,讓我們從原理上細致的了解它們,能夠讓我們在深度項目開發中獲益非淺。
????通過分析Hashtable就知道,synchronized是針對整張Hash表的,即每次鎖住整張表讓線程獨占,ConcurrentHashMap允許多個修改操作并發進行,其關鍵在于使用了鎖分離技術。它使用了多個鎖來控制對hash表的不同部分進行的修改。ConcurrentHashMap內部使用段(Segment)來表示這些不同的部分,每個段其實就是一個小的hash?table,它們有自己的鎖。只要多個修改操作發生在不同的段上,它們就可以并發進行。
有些方法需要跨段,比如size()和containsValue(),它們可能需要鎖定整個表而而不僅僅是某個段,這需要按順序鎖定所有段,操作完畢后,又按順序釋放所有段的鎖。這里“按順序”是很重要的,否則極有可能出現死鎖,在ConcurrentHashMap內部,段數組是final的,并且其成員變量實際上也是final的,但是,僅僅是將數組聲明為final的并不保證數組成員也是final的,這需要實現上的保證。這可以確保不會出現死鎖,因為獲得鎖的順序是固定的。
鎖分離
通過HashMap分析知道,Synchronized是針對整張hash表的,即每次鎖住整張表,而ConcurrentHashMap允許多個修改操作并發進行。其關鍵是采用了鎖分離技術。ConCurrentHashMap內部使用段(segment)來表示這些不同的部分,每個段就是一個小的hashtable。但是size()和containsValue(),則可能需要鎖住整個表。
Hashmap與ConcurrentHashMap的結構比較
每一個segment管理著一個小的hashtable,所以在多線程下訪問ConcurrentHashMap,只需對對應segment進行加鎖即可。
?ConcurrentHashMap的詳細結構
二、應用場景
當有一個大數組時需要在多個線程共享時就可以考慮是否把它給分層多個節點了,避免大鎖。并可以考慮通過hash算法進行一些模塊定位。
其實不止用于線程,當設計數據表的事務時(事務某種意義上也是同步機制的體現),可以把一個表看成一個需要同步的數組,如果操作的表數據太多時就可以考慮事務分離了(這也是為什么要避免大表的出現),比如把數據進行字段拆分,水平分表等.
三、源碼解讀
?ConcurrentHashMap中主要實體類就是三個:ConcurrentHashMap(整個Hash表),Segment(桶),HashEntry(節點)
我們再來具體了解一下Segment的數據結構:
static final class Segment<K,V> extends ReentrantLock implements Serializable {transient volatile int count;transient int modCount;transient int threshold;transient volatile HashEntry<K,V>[] table;final float loadFactor; }詳細解釋一下Segment里面的成員變量的意義:
- count:Segment中元素的數量
- modCount:對table的大小造成影響的操作的數量(比如put或者remove操作)
- threshold:閾值,Segment里面元素的數量超過這個值依舊就會對Segment進行擴容
- table:鏈表數組,數組中的每一個元素代表了一個鏈表的頭部
- loadFactor:負載因子,用于確定threshold
Segment中的元素是以HashEntry的形式存放在鏈表數組中的,看一下HashEntry的結構:
static final class HashEntry<K,V> {final K key;final int hash;volatile V value;final HashEntry<K,V> next; }可以看到HashEntry的一個特點,除了value以外,其他的幾個變量都是final的,這樣做是為了防止鏈表結構被破壞,出現ConcurrentModification的情況。
ConcurrentHashMap的初始化
下面我們來結合源代碼來具體分析一下ConcurrentHashMap的實現,先看下初始化方法:
public ConcurrentHashMap(int initialCapacity,float loadFactor, int concurrencyLevel) {if (!(loadFactor > 0) || initialCapacity < 0 || concurrencyLevel <= 0)throw new IllegalArgumentException();if (concurrencyLevel > MAX_SEGMENTS)concurrencyLevel = MAX_SEGMENTS;// Find power-of-two sizes best matching argumentsint sshift = 0;int ssize = 1;while (ssize < concurrencyLevel) {++sshift;ssize <<= 1;}segmentShift = 32 - sshift;segmentMask = ssize - 1;this.segments = Segment.newArray(ssize);if (initialCapacity > MAXIMUM_CAPACITY)initialCapacity = MAXIMUM_CAPACITY;int c = initialCapacity / ssize;if (c * ssize < initialCapacity)++c;int cap = 1;while (cap < c)cap <<= 1;for (int i = 0; i < this.segments.length; ++i)this.segments[i] = new Segment<K,V>(cap, loadFactor); }? CurrentHashMap的初始化一共有三個參數,一個initialCapacity,表示初始的容量,一個loadFactor,表示負載參數,最后一個是concurrentLevel,代表ConcurrentHashMap內部的Segment的數量,ConcurrentLevel一經指定,不可改變,后續如果ConcurrentHashMap的元素數量增加導致ConrruentHashMap需要擴容,ConcurrentHashMap不會增加Segment的數量,而只會增加Segment中鏈表數組的容量大小,這樣的好處是擴容過程不需要對整個ConcurrentHashMap做rehash,而只需要對Segment里面的元素做一次rehash就可以了。
整個ConcurrentHashMap的初始化方法還是非常簡單的,先是根據concurrentLevel來new出Segment,這里Segment的數量是不大于concurrentLevel的最大的2的指數,就是說Segment的數量永遠是2的指數個,這樣的好處是方便采用移位操作來進行hash,加快hash的過程。接下來就是根據intialCapacity確定Segment的容量的大小,每一個Segment的容量大小也是2的指數,同樣使為了加快hash的過程。
這邊需要特別注意一下兩個變量,分別是segmentShift和segmentMask,這兩個變量在后面將會起到很大的作用,假設構造函數確定了Segment的數量是2的n次方,那么segmentShift就等于32減去n,而segmentMask就等于2的n次方減一。
總的來說,ConcurrentHashMap對讀操作沒有加鎖,對寫操作只加鎖一小部分,保證其并發性。
android ConcurrentHashMap的使用就講完了。
就這么簡單。
總結
以上是生活随笔為你收集整理的android ConcurrentHashMap的使用的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: android 浅复制与深复制
- 下一篇: android Arrays.fill(