ConcurrentHashMap

  • Java5.0以后 在java.util.concurrent包中提供了多種并發(fā)容器類來改進同步容器的性能。 \color{red}{紅色}
  • ConcurrentHashMap同步容器類是Java5新增的一個線程安全的Hash表。對于多線程的操作,介于HashMap和HashTable之間。內(nèi)部通過采用\color{red}{鎖分段機制}將鎖進行細粒度化代替HashTable的獨占鎖,使得多個線程能夠同時操作哈希表,提高性能。
  • 此包還提供了設(shè)計用于多線程上下文中的 Collection 實現(xiàn): ConcurrentHashMap、ConcurrentSkipListMap、ConcurrentSkipListSet、 CopyOnWriteArrayListCopyOnWriteArraySet
    當(dāng)期望許多線程訪問一個給 定 collection 時,ConcurrentHashMap 通常優(yōu)于同步的 HashMap,
    ConcurrentSkipListMap 通常優(yōu)于同步的 TreeMap。
    當(dāng)期望的讀數(shù)和遍歷遠遠 大于列表的更新時,CopyOnWriteArrayList 優(yōu)于同步的 ArrayList。

Collectons.syn***(new HashMap()) 或 HashTable

concurrentLevel 鎖分段級別 默認16段
每一段都是Segment,后面跟著數(shù)組+鏈表
優(yōu)點: 每個段都是獨立的鎖,支持多個線程同時訪問,多個線程互不影響。


示意圖

1.8 分段鎖 改成CAS 無鎖算法。
CAS不阻塞,不存在上下文切換問題。

//默認初始化容量
static final int DEFAULT_INITIAL_CAPACITY = 16;
 
//默認加載因子
static final float DEFAULT_LOAD_FACTOR = 0.75f;
 
//默認并發(fā)級別
static final int DEFAULT_CONCURRENCY_LEVEL = 16;
 
//集合最大容量
static final int MAXIMUM_CAPACITY = 1 << 30;
 
//分段鎖的最小數(shù)量
static final int MIN_SEGMENT_TABLE_CAPACITY = 2;
 
//分段鎖的最大數(shù)量
static final int MAX_SEGMENTS = 1 << 16;
 
//加鎖前的重試次數(shù)
static final int RETRIES_BEFORE_LOCK = 2;
 
//分段鎖的掩碼值
final int segmentMask;
 
//分段鎖的移位值
final int segmentShift;
 
//分段鎖數(shù)組
final Segment<K,V>[] segments;

分段鎖的內(nèi)部結(jié)構(gòu)

//分段鎖
static final class Segment<K,V> extends ReentrantLock implements Serializable {
  //自旋最大次數(shù)
  static final int MAX_SCAN_RETRIES = Runtime.getRuntime().availableProcessors() > 1 ? 64 : 1;
  //哈希表
  transient volatile HashEntry<K,V>[] table;
  //元素總數(shù)
  transient int count;
  //修改次數(shù)
  transient int modCount;
  //元素閥值
  transient int threshold;
  //加載因子
  final float loadFactor;
  //省略以下內(nèi)容
  ...
}

Segment是ConcurrentHashMap的靜態(tài)內(nèi)部類,可以看到它繼承自ReentrantLock,因此它在本質(zhì)上是一個鎖。它在內(nèi)部持有一個HashEntry數(shù)組(哈希表),并且保證所有對該數(shù)組的增刪改查方法都是線程安全的,具體是怎樣實現(xiàn)的后面會講到。所有對ConcurrentHashMap的增刪改查操作都可以委托Segment來進行,因此ConcurrentHashMap能夠保證在多線程環(huán)境下是安全的。又因為不同的Segment是不同的鎖,所以多線程可以同時操作不同的Segment,也就意味著多線程可以同時操作ConcurrentHashMap,這樣就能避免HashTable的缺陷,從而極大的提高性能。

初始化操作

//核心構(gòu)造器
public ConcurrentHashMap(int initialCapacity, float loadFactor, int concurrencyLevel) {
  if (!(loadFactor > 0) || initialCapacity < 0 || concurrencyLevel <= 0) {
    throw new IllegalArgumentException();
  }
  //確保并發(fā)級別不大于限定值
  if (concurrencyLevel > MAX_SEGMENTS) {
    concurrencyLevel = MAX_SEGMENTS;
  }
  int sshift = 0;
  int ssize = 1;
  //保證ssize為2的冪, 且是最接近的大于等于并發(fā)級別的數(shù)
  while (ssize < concurrencyLevel) {
    ++sshift;
    ssize <<= 1;
  }
  //計算分段鎖的移位值
  this.segmentShift = 32 - sshift;
  //計算分段鎖的掩碼值
  this.segmentMask = ssize - 1;
  //總的初始容量不能大于限定值
  if (initialCapacity > MAXIMUM_CAPACITY) {
    initialCapacity = MAXIMUM_CAPACITY;
  }
  //獲取每個分段鎖的初始容量
  int c = initialCapacity / ssize;
  //分段鎖容量總和不小于初始總?cè)萘?  if (c * ssize < initialCapacity) {
    ++c;
  }
  int cap = MIN_SEGMENT_TABLE_CAPACITY;
  //保證cap為2的冪, 且是最接近的大于等于c的數(shù)
  while (cap < c) {
    cap <<= 1;
  }
  //新建一個Segment對象模版
  Segment<K,V> s0 = new Segment<K,V>(loadFactor, (int)(cap * loadFactor), (HashEntry<K,V>[])new HashEntry[cap]);
  //新建指定大小的分段鎖數(shù)組
  Segment<K,V>[] ss = (Segment<K,V>[])new Segment[ssize];
  //使用UnSafe給數(shù)組第0個元素賦值
  UNSAFE.putOrderedObject(ss, SBASE, s0);
  this.segments = ss;
}

ConcurrentHashMap有多個構(gòu)造器,但是上面貼出的是它的核心構(gòu)造器,其他構(gòu)造器都通過調(diào)用它來完成初始化。核心構(gòu)造器需要傳入三個參數(shù),分別是初始容量,加載因子和并發(fā)級別。在前面介紹成員變量時我們可以知道默認的初始容量為16,加載因子為0.75f,并發(fā)級別為16。現(xiàn)在我們看到核心構(gòu)造器的代碼,首先是通過傳入的concurrencyLevel來計算出ssize,ssize是Segment數(shù)組的長度,它必須保證是2的冪,這樣就可以通過hash&ssize-1來計算分段鎖在數(shù)組中的下標(biāo)。由于傳入的concurrencyLevel不能保證是2的冪,所以不能直接用它來當(dāng)作Segment數(shù)組的長度,因此我們要找到一個最接近concurrencyLevel的2的冪,用它來作為數(shù)組的長度。假如現(xiàn)在傳入的concurrencyLevel=15,通過上面代碼可以計算出ssize=16,sshift=4。接下來立馬可以算出segmentShift=16,segmentMask=15。注意這里的segmentShift是分段鎖的移位值,segmentMask是分段鎖的掩碼值,這兩個值是用來計算分段鎖在數(shù)組中的下標(biāo),在下面我們會講到。在算出分段鎖的個數(shù)ssize之后,就可以根據(jù)傳入的總?cè)萘縼碛嬎忝總€分段鎖的容量,它的值c = initialCapacity / ssize。分段鎖的容量也就是HashEntry數(shù)組的長度,同樣也必須保證是2的冪,而上面算出的c的值不能保證這一點,所以不能直接用c作為HashEntry數(shù)組的長度,需要另外找到一個最接近c的2的冪,將這個值賦給cap,然后用cap來作為HashEntry數(shù)組的長度?,F(xiàn)在我們有了ssize和cap,就可以新建分段鎖數(shù)組Segment[]和元素數(shù)組HashEntry[]了。注意,與JDK1.6不同是的,在JDK1.7中只新建了Segment數(shù)組,并沒有對它初始化,初始化Segment的操作留到了插入操作時進行。

元素

//根據(jù)哈希碼獲取分段鎖
@SuppressWarnings("unchecked")
private Segment<K,V> segmentForHash(int h) {
  long u = (((h >>> segmentShift) & segmentMask) << SSHIFT) + SBASE;
  return (Segment<K,V>) UNSAFE.getObjectVolatile(segments, u);
}
 
//根據(jù)哈希碼獲取元素
@SuppressWarnings("unchecked")
static final <K,V> HashEntry<K,V> entryForHash(Segment<K,V> seg, int h) {
  HashEntry<K,V>[] tab;
  return (seg == null || (tab = seg.table) == null) ? null :
  (HashEntry<K,V>) UNSAFE.getObjectVolatile(tab, ((long)(((tab.length - 1) & h)) << TSHIFT) + TBASE);
}

public V get(Object key) {
    int hash = hash(key); // throws NullPointerException if key null
    return segmentFor(hash).get(key, hash);
}
  • 通過哈希碼計算分段鎖在數(shù)組中的下標(biāo):(h >>> segmentShift) & segmentMask。
  • 通過哈希碼計算元素在數(shù)組中的下標(biāo):(tab.length - 1) & h。

hashMap
參考連接

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容