Java7 ConcurrentHashMap總結(jié)分析

ConcurrentHashMap

在JAVA線程安全的并發(fā)集合有HashTable,而HashTable
是一種通過在方法上使用Synchronize的簡單粗暴的方式進行保證線程的安全燃逻,ConcurrentHashMap則是一個高效的并發(fā)集合蝙云。ConcurrentHashMap不是簡單粗暴的在方法上使用Synchronize,而是通過分段(segment) 鎖的方式進行處理并發(fā)問題在旱。ConcurrentHashMap的鎖機制更類似于我們數(shù)據(jù)庫中的行鎖偷线,而HashTable則是整張表鎖磨确。下圖為ConcurrentHashMap的數(shù)據(jù)結(jié)構(gòu)。

結(jié)構(gòu).png
取模算法

ConcurrentHashMap獲取Segment或者在Segment中獲取Entry都是是通過mod求余來確定當前的Segment或者Entry.而在ConcurrentHashMap不是使用“%”的方式來進行求余值淋昭,而是通過&位運算的方式求余值俐填。但是這種&位運算求余的方式必須是2n
公式:a%(2n)是等價于a&(2n-1)。

System.out.println("345%16="+(345%16)+" 等價于: 345&(16-1)="+(345&(16-1)));
輸出結(jié)果:
345%16=9 等價于: 345&(16-1)=9
ConcurrentHashMap初始化
  public ConcurrentHashMap() {
        this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL);
    }

    @SuppressWarnings("unchecked")
    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 arguments
        int sshift = 0;
        int ssize = 1;
        //根據(jù)并發(fā)級別進行計算Segment的size和位移量
        while (ssize < concurrencyLevel) {
            ++sshift;
            ssize <<= 1;
        }
        //計算key&(2n-1)的值和key左移量
        this.segmentShift = 32 - sshift;
        this.segmentMask = ssize - 1;
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        //16384
        int c = initialCapacity / ssize;
        if (c * ssize < initialCapacity)
            ++c;
        int cap = MIN_SEGMENT_TABLE_CAPACITY;
        while (cap < c)
            cap <<= 1; 
        // create segments and segments[0]
        Segment<K,V> s0 =
            new Segment<K,V>(loadFactor, (int)(cap * loadFactor),
                             (HashEntry<K,V>[])new HashEntry[cap]);
        Segment<K,V>[] ss = (Segment<K,V>[])new Segment[ssize];
        UNSAFE.putOrderedObject(ss, SBASE, s0); // ordered write of segments[0]
        this.segments = ss;
    }

我們通過源碼的初始化方法進行分析得出:

  • ConcurrentHashMap初始化的時候會傳入:
    (1)initialCapacity初始容量翔忽,而initialCapacity主要用于計算初始化Segment中的Table的初始化容量英融。
    (2) loadFactor負載因子,則是用來計算Table的擴展闊值歇式。當Table達到闊值的時候就會進行動態(tài)擴展驶悟。
    (3) concurrencyLevel并發(fā)級別,則是用來確定Segment的大小材失。默認系統(tǒng)設(shè)置16個segment痕鳍。
  • 初始化的時候默認先計算segmentshift和sgmentMask,而上面已經(jīng)說過ConcurrentHashMap是通過&運算求余的方式進行確定當前Key值對應(yīng)的位置。那么segmentShift則是(2n-1),sgmentMask則是key左移的值笼呆,ConcurrentHashMap是對key左移sgmentMask后再進行&操作熊响。
  • 默認配置下創(chuàng)建1個Segment,而Segment是不可以進行動態(tài)擴容的诗赌。但是Segment中的Table根據(jù)闊值可以進行動態(tài)擴容汗茄。
put
  • 創(chuàng)建Segment
  @SuppressWarnings("unchecked")
    public V put(K key, V value) {
        Segment<K,V> s;
        if (value == null)
            throw new NullPointerException();
        //計算hash值,在hash方法中會對對象中的hashCode進行Hash
        int hash = hash(key);
        //對hash值進行左位移后铭若,求余
        int j = (hash >>> segmentShift) & segmentMask;
       //判斷當這個Segment是否為Null,如果為Null創(chuàng)建一個Segment
        if ((s = (Segment<K,V>)UNSAFE.getObject          // nonvolatile; recheck
             (segments, (j << SSHIFT) + SBASE)) == null) //  in ensureSegment
            s = ensureSegment(j);
        return s.put(key, hash, value, false);
    }

   @SuppressWarnings("unchecked")
    private Segment<K,V> ensureSegment(int k) {
        final Segment<K,V>[] ss = this.segments;
        long u = (k << SSHIFT) + SBASE; // raw offset
        Segment<K,V> seg;
        //判斷是否存在segament洪碳,因為不是線程安全的所以需要多次檢測是否存在Segment
        if ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u)) == null) {
            Segment<K,V> proto = ss[0]; // use segment 0 as prototype
            //初始化Segment中Table的闊值、容量叼屠、負載因子
            int cap = proto.table.length;
            float lf = proto.loadFactor;
            int threshold = (int)(cap * lf);
            //創(chuàng)建Table
            HashEntry<K,V>[] tab = (HashEntry<K,V>[])new HashEntry[cap];
            //判斷是否存在segament瞳腌,因為不是線程安全的所以需要多次檢測是否存在Segment
            if ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u))
                == null) { // recheck
                Segment<K,V> s = new Segment<K,V>(lf, threshold, tab);
                while ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u))
                       == null) {
                    //使用CAS方式進行插入Segment
                    if (UNSAFE.compareAndSwapObject(ss, u, null, seg = s))
                        break;
                }
            }
        }
        return seg;
    }

    private int hash(Object k) {
        int h = hashSeed;

        if ((0 != h) && (k instanceof String)) {
            return sun.misc.Hashing.stringHash32((String) k);
        }

        h ^= k.hashCode();

        // Spread bits to regularize both segment and index locations,
        // using variant of single-word Wang/Jenkins hash.
        h += (h <<  15) ^ 0xffffcd7d;
        h ^= (h >>> 10);
        h += (h <<   3);
        h ^= (h >>>  6);
        h += (h <<   2) + (h << 14);
        return h ^ (h >>> 16);
    }

通過對源碼的分析我們可以看到因為在初始化ConcurrentHashMap的時候只有一個Segment,當計算出key的hash值后镜雨,若該Hash值沒有對應(yīng)的Segment的時候會去創(chuàng)建一個Segment嫂侍,而因為在創(chuàng)建Segment是非加鎖的方式,因此在創(chuàng)建Segment的時候會多次判斷是否存在Segment冷离,在最后還使用了CAS的方式進行插入到Segment的數(shù)組中吵冒。

CAS的使用的標準范式纯命,在循環(huán)中使用CAS操作西剥。保證CAS一定執(zhí)行。

  • 插入值
    ``

      final V put(K key, int hash, V value, boolean onlyIfAbsent) {
          //獲取重入鎖
          HashEntry<K,V> node = tryLock() ? null :
              scanAndLockForPut(key, hash, value);
          V oldValue;
          try {
              HashEntry<K,V>[] tab = table;
              int index = (tab.length - 1) & hash;
              //根據(jù)計算的index獲取Entry
              HashEntry<K,V> first = entryAt(tab, index);
              //循環(huán)所有Entry
              for (HashEntry<K,V> e = first;;) {
                  if (e != null) {
                      K k;
                      //判斷值是否存在相同的Key或者Hash值
                      if ((k = e.key) == key ||
                          (e.hash == hash && key.equals(k))) {
                          oldValue = e.value;
                          //判斷是否為不存在插入亿汞。
                          if (!onlyIfAbsent) {
                              e.value = value;
                              ++modCount;
                          }
                          break;
                      }
                      //迭代下一個
                      e = e.next;
                  }
                  else {
                      if (node != null)
                          node.setNext(first);
                      else
                          node = new HashEntry<K,V>(hash, key, value, first);
                      int c = count + 1;
                      //判斷是否達到闊值瞭空,
                      if (c > threshold && tab.length < MAXIMUM_CAPACITY)
                          //達到闊值,擴容并且重新計算HASH值
                          rehash(node);
                      else
                          //如果沒有達到闊值則添加到node的下一個
                          setEntryAt(tab, index, node);
                      ++modCount;
                      count = c;
                      oldValue = null;
                      break;
                  }
              }
          } finally {
              //釋放鎖
              unlock();
          }
          return oldValue;
      }
    

``
(1)這個方法有兩個出口疗我,一個是 tryLock() 成功了咆畏,循環(huán)終止,另一個就是重試次數(shù)超過了 MAX_SCAN_RETRIES吴裤,進到 lock() 方法旧找,此方法會阻塞等待,直到成功拿到獨占鎖麦牺。
(2)循環(huán)table鏈钮蛛,如果存在HashEntry時,則進行判斷是否需要更新剖膳,如果存相等的Hash和Key魏颓,則判斷onlyIfAbsent是否為False,若為False則更新當前的值并且跳出循環(huán)返回吱晒。因為put方法有putIfAbsent和put公共甸饱,而putIfAbsent則只有當前值不存在時插入,如果存在則返回舊值,而put則是無論任何時候都會更新舊值叹话,并且將舊值返回偷遗。如果不存相等的Hash和Key。判斷當前節(jié)點是否頭節(jié)點驼壶,如果是則設(shè)置為頭節(jié)點鹦肿。然后判斷是否需要對Table進行擴容,如果需要則從新計算hash值并且將新值增加到鏈中辅柴。如果不需要擴容則直接添加到鏈中箩溃。

get
    public V get(Object key) {
        Segment<K,V> s; // manually integrate access methods to reduce overhead
        HashEntry<K,V>[] tab;
        int h = hash(key);
        long u = (((h >>> segmentShift) & segmentMask) << SSHIFT) + SBASE;
        if ((s = (Segment<K,V>)UNSAFE.getObjectVolatile(segments, u)) != null &&
            (tab = s.table) != null) {
            for (HashEntry<K,V> e = (HashEntry<K,V>) UNSAFE.getObjectVolatile
                     (tab, ((long)(((tab.length - 1) & h)) << TSHIFT) + TBASE);
                 e != null; e = e.next) {
                K k;
                if ((k = e.key) == key || (e.hash == h && key.equals(k)))
                    return e.value;
            }
        }
        return null;
    }

計算 hash 值,找到 segment 數(shù)組中的具體位置.根據(jù) hash 找到數(shù)組中具體的位置,循環(huán)鏈查找相等的值并且返回

size
 public int size() {
        // Try a few times to get accurate count. On failure due to
        // continuous async changes in table, resort to locking.
        final Segment<K,V>[] segments = this.segments;
        int size;
        boolean overflow; // true if size overflows 32 bits
        long sum;         // sum of modCounts
        long last = 0L;   // previous sum
        int retries = -1; // first iteration isn't retry
        try {
            for (;;) {
                if (retries++ == RETRIES_BEFORE_LOCK) {
                    for (int j = 0; j < segments.length; ++j)
                        ensureSegment(j).lock(); // force creation
                }
                sum = 0L;
                size = 0;
                overflow = false;
                for (int j = 0; j < segments.length; ++j) {
                    Segment<K,V> seg = segmentAt(segments, j);
                    if (seg != null) {
                        sum += seg.modCount;
                        int c = seg.count;
                        if (c < 0 || (size += c) < 0)
                            overflow = true;
                    }
                }
                if (sum == last)
                    break;
                last = sum;
            }
        } finally {
            if (retries > RETRIES_BEFORE_LOCK) {
                for (int j = 0; j < segments.length; ++j)
                    segmentAt(segments, j).unlock();
            }
        }
        return overflow ? Integer.MAX_VALUE : size;
    }

在獲取ConcurrentHashMap的size時候碌嘀,是獲取兩次size的值判斷是否一致涣旨,如果不是一致的情況下,再進行加鎖操作股冗。所以在高并發(fā)寫入的情況下避免使用Size方法霹陡,以免由于加鎖導(dǎo)致性能問題。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末止状,一起剝皮案震驚了整個濱河市烹棉,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌怯疤,老刑警劉巖浆洗,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異集峦,居然都是意外死亡伏社,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進店門塔淤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來摘昌,“玉大人,你說我怎么就攤上這事高蜂〈侠瑁” “怎么了?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵备恤,是天一觀的道長稿饰。 經(jīng)常有香客問我,道長烘跺,這世上最難降的妖魔是什么湘纵? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任,我火速辦了婚禮滤淳,結(jié)果婚禮上梧喷,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好铺敌,可當我...
    茶點故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布汇歹。 她就那樣靜靜地躺著,像睡著了一般偿凭。 火紅的嫁衣襯著肌膚如雪产弹。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天弯囊,我揣著相機與錄音痰哨,去河邊找鬼。 笑死匾嘱,一個胖子當著我的面吹牛斤斧,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播霎烙,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼撬讽,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了悬垃?” 一聲冷哼從身側(cè)響起游昼,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎尝蠕,沒想到半個月后烘豌,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡趟佃,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年扇谣,在試婚紗的時候發(fā)現(xiàn)自己被綠了昧捷。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片闲昭。...
    茶點故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖靡挥,靈堂內(nèi)的尸體忽然破棺而出序矩,到底是詐尸還是另有隱情,我是刑警寧澤跋破,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布簸淀,位于F島的核電站,受9級特大地震影響毒返,放射性物質(zhì)發(fā)生泄漏租幕。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一拧簸、第九天 我趴在偏房一處隱蔽的房頂上張望劲绪。 院中可真熱鬧,春花似錦、人聲如沸贾富。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽颤枪。三九已至汗捡,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間畏纲,已是汗流浹背扇住。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工技潘, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留最楷,地道東北人饰序。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓急前,卻偏偏與公主長得像纲堵,于是被迫代替她去往敵國和親宾舅。 傳聞我的和親對象是個殘疾皇子骇径,可洞房花燭夜當晚...
    茶點故事閱讀 42,722評論 2 345

推薦閱讀更多精彩內(nèi)容