相對於HashMap,ConcurrentHashMap提供了內部實現的併發支持。使得開發者在多線程應用中訪問ConcurrentHashMap時,不必使用synchronized同步代碼塊。
//Initialize ConcurrentHashMap instance
ConcurrentHashMap<String, Integer> m = new ConcurrentHashMap<String, Integer>();
//Print all values stored in ConcurrentHashMap instance
for each (Entry<String, Integer> e : m.entrySet())
{
system.out.println(e.getKey()+"="+e.getValue());
}
上述代碼是在多線程應用中創建並使用ConcurrentHashMap的“理論上可行”的示例。這裏的“理論上可行”是指,上述代碼雖然能夠保證線程安全,但是還是會降低程序性能。但ConcurentHashMap是爲了保證線程安全(優於HashMap)的同時改善性能(優於Hashtable),不是嗎?
哪裏有問題呢?
爲了搞清楚,我們需要理解ConcurrentHashMap類的內部工作原理。我們最好是從構造函數參數開始。ConcurrentHashMap完整的構造函數需要三個參數:initialCapacity(初始容量),loadFactory(加載因子),concurrencyLevel(併發級別)。
- initialCapacity:初始容量。ConcurrentHashMap的實現基於加載因子,進行內部分配以容納這麼多元素。
- loadFactor:加載因子(表密度),用於建立初始表的大小
- concurrencyLevel:併發級別,表示預計的同步更新線程的數量。
前兩個參數比較容易理解;併發級別表示分片(shard)的數量,用於在ConcurrentHashMap內部分爲相應的分區,同時相同數量的線程被創建,用於在分片級別保證線程安全。
concurrencyLevel的默認值爲16。這意味着我們只要使用默認構造函數創建一個ConcurrentHashMap時,就會創建16個分片——在我們向map中加入任何鍵值對之前。它同時意味着各種內部類的實例被創建,如ConcurrentHashMap$Segment, ConcurrentHashMap$HashEntry[] 和ReentrantLock$NofairSync。
多數情況下,一個分片已經足夠處理通常鍵值對數量的多線程,同時性能也會被優化。創建多個分片只會使得內部實現更加複雜,同時引入許多不必要的對象,這一切都不利於改善性能。
每個使用默認構造函數創建的concurrent hashmap,創建冗餘對象的比例約爲1到50。例如,沒創建100個ConcurrentHashMap實例,將會創建5000個冗餘對象。
基於以上討論,一個建議是更明智地使用構造函數參數,以減少冗餘對象,同時提高性能。
ConcurrentHashMap更好的初始化方式:
ConcurrentHashMap<String, Integer> instance = new ConcurrentHashMap<String, Integer>(16, 0.9f, 1);
初始容量16能夠在擴容發生之前容納足夠多的元素。加載因子0.9保證了ConcurrentHashMap內部的緻密堆積,以優化內存使用。併發級別設置爲1,使得只有一個分片被創建和維護。
請注意,如果你的高併發應用程序更新ConcurrentHashMap的頻率很高,你應當考慮增大concurrencyLevel,具體數值應該進行嚴謹的計算、測試以評估。
譯者注:JDK1.8起通過默認構造函數創建的ConcurrentHashMap,其concurrencyLevel已被設置爲1。
/**
* Creates a new, empty map with an initial table size based on
* the given number of elements ({@code initialCapacity}) and
* initial table density ({@code loadFactor}).
*
* @param initialCapacity the initial capacity. The implementation
* performs internal sizing to accommodate this many elements,
* given the specified load factor.
* @param loadFactor the load factor (table density) for
* establishing the initial table size
* @throws IllegalArgumentException if the initial capacity of
* elements is negative or the load factor is nonpositive
*
* @since 1.6
*/
public ConcurrentHashMap(int initialCapacity, float loadFactor) {
this(initialCapacity, loadFactor, 1);
}
原文寫於JDK 1.8發佈之前,可以作爲JDK 1.8如此優化的解釋。
原文鏈接:Java ConcurrentHashMap Best Practices
ConcurrentHashMap源碼值得再讀。相關文章: