Java ConcurrentHashMap 最佳實踐

相對於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(併發級別)。

  1. initialCapacity:初始容量。ConcurrentHashMap的實現基於加載因子,進行內部分配以容納這麼多元素。
  2. loadFactor:加載因子(表密度),用於建立初始表的大小
  3. 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源碼值得再讀。相關文章:

  1. 探索 ConcurrentHashMap 高併發性的實現機制 
  2. 深入分析ConcurrentHashMap 
  3. Java集合---ConcurrentHashMap原理分析 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章