qileilove

          blog已經(jīng)轉(zhuǎn)移至github,大家請訪問 http://qaseven.github.io/

          java的HashMap與ConcurrentHashMap

           好像今天沒有什么源碼讀,那么就來看看java的這兩種HashMap有啥不一樣的地方吧,在這之前先普及一下HashMap的一些基本知識:

            (1)放入HashMap的元素是key-value對。

            (2)底層說白了就是以前數(shù)據(jù)結(jié)構(gòu)課程講過的散列結(jié)構(gòu)。

            (3)要將元素放入到hashmap中,那么key的類型必須要實現(xiàn)實現(xiàn)hashcode方法,默認(rèn)這個方法是根據(jù)對象的地址來計算的,具體我也記不太清楚了,接著還必須覆蓋對象的equal方法。

            用一張圖來表示一下散列結(jié)構(gòu)吧:

            在這里hashCode函數(shù)就是用于確定當(dāng)前key應(yīng)該放在hash桶里面的位置,這里hash桶可以看成是一個數(shù)組,最簡單的通過一些取余的方法就能用來確認(rèn)key應(yīng)該擺放的位置,而equal函數(shù)則是為了與后面的元素之間判斷重復(fù)。

            好了,這里我們接下來來看看java的這兩種類庫的用法吧:

            由于他們都實現(xiàn)了Map接口,將元素放進(jìn)去的方法就是put(a,b),這里我們先來分析比較簡單的HashMap吧:

              public V put(K key, V value) {
                  if (key == null)
                      return putForNullKey(value);
                  int hash = hash(key);  //獲取當(dāng)前key的hash值
                  int i = indexFor(hash, table.length);  //返回在hash桶里面的位置
                  for (Entry<K,V> e = table[i]; e != null; e = e.next) {  //遍歷當(dāng)前hansh桶后面的元素
                      Object k;
                      if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {  //如果有相同的key,那么需要替換value
                          V oldValue = e.value;
                          e.value = value;
                          e.recordAccess(this);
                          return oldValue;  //返回以前的value
                      }
                  }

                  modCount++;
                  addEntry(hash, key, value, i);  //放入entry
                  return null;
              }

            這個函數(shù)其實本身還是很簡單的,首先通過hash函數(shù)獲取當(dāng)前key的hash值,不過這里需要注意的是,對hashCode方法返回的值HashMap本身還會進(jìn)行一些處理,具體什么樣子的就不細(xì)說了,然后再調(diào)用indexFor方法用于確定當(dāng)前key應(yīng)該屬于當(dāng)前Hash桶的位置,接著就是遍歷當(dāng)前桶后面的鏈表了,這里equal方法就派上用場了,這里看到如果equal是相等的話,那么就直接用新的value來替換原來的value就好了。。。

            當(dāng)然最多的情況還是,桶后面的鏈表沒有與當(dāng)前的key相同的,那么這個時候就需要調(diào)用addEntry方法,將要加入的key-value放入到當(dāng)前的結(jié)構(gòu)中了,那么接下來來看看這個方法的定義吧:

              void addEntry(int hash, K key, V value, int bucketIndex) {
                  if ((size >= threshold) && (null != table[bucketIndex])) {
                      resize(2 * table.length);  //相當(dāng)于重新設(shè)置hash桶
                      hash = (null != key) ? hash(key) : 0;
                      bucketIndex = indexFor(hash, table.length);
                  }

                  createEntry(hash, key, value, bucketIndex);  //創(chuàng)建新的entry,并將它加入到當(dāng)前的桶后面的鏈表中
              }

            其實這個方法很簡單,首先來判斷當(dāng)前的桶的大小,如果覺得太小的話,那么需要擴(kuò)充當(dāng)前桶的大小,這樣可以讓添加元素存放的更離散化一些,優(yōu)化擦入和尋找的效率。

            然后就是創(chuàng)建一個新的entry,用于保存要擦入的key和value,然后再將其鏈到應(yīng)該放的桶的鏈表上就好了。。

            好了,到這里位置,整個HashMap的擦入元素的過程就已經(jīng)看的很清楚了,在整個這個過程中沒有看到有加鎖的過程,因此可以說明HashMap是不支持并發(fā)的,不是線程安全的,在并發(fā)的環(huán)境下使用會產(chǎn)生一些不一致的問題。。。

            因此java新的concurrent類庫中就有了ConcurrentHashMap用于在并發(fā)環(huán)境中使用。。

            那么我們再來看看ConcurrentHashMap的put操作是怎么搞的吧:

              public V put(K key, V value) {
                  Segment<K,V> s;
                  if (value == null)
                      throw new NullPointerException();
                  int hash = hash(key);  //獲取hash值
                  int j = (hash >>> segmentShift) & segmentMask;
                  if ((s = (Segment<K,V>)UNSAFE.getObject          // nonvolatile; recheck
                       (segments, (j << SSHIFT) + SBASE)) == null) //  in ensureSegment  //用于獲取相應(yīng)的片段
                      s = ensureSegment(j);   //這里表示沒有這個片段,那么需要創(chuàng)建這個片段
                  return s.put(key, hash, value, false);  //這里就有分段加鎖的策略
              }

            這里剛開始跟HashMap都差不太多吧,無非是先獲取當(dāng)前key的hash值,但是接下來進(jìn)行的工作就不太一樣了,這里就有了一個分段的概念:

            ConcurrentHashMap將整個Hash桶進(jìn)行了分段,也就是將這個大的數(shù)組分成了幾個小的片段,而且每個小的片段上面都有鎖存在,那么在擦入元素的時候就需要先找到應(yīng)該插入到哪一個片段,然后再在這個片段上面進(jìn)行擦入,而且這里還需要獲取鎖。。。。

            那我們來看看這個segment的put方法吧:

          final V put(K key, int hash, V value, boolean onlyIfAbsent) {
                   //這里的鎖是計數(shù)鎖,同一個鎖可以被同一個線程獲取多次,但是不能被不同的線程獲取
                      HashEntry<K,V> node = tryLock() ? null :   //如果獲取了當(dāng)前的segment的鎖,那么node為null,待會自己分配就好了
                          scanAndLockForPut(key, hash, value);  //如果沒有加上鎖,那么等吧,有可能的話還要分配entry,反正有時間干嘛不多做一些事情
                      V oldValue;
                      try {
                       //這里表示已經(jīng)獲取了鎖,那么將在相應(yīng)的位置放入entry
                          HashEntry<K,V>[] tab = table;
                          int index = (tab.length - 1) & hash;
                          HashEntry<K,V> first = entryAt(tab, index);  //找到存放entry的桶,然后獲取第一個entry
                          for (HashEntry<K,V> e = first;;) {  //從當(dāng)前的第一個元素開始
                              if (e != null) {
                                  K k;
                                  if ((k = e.key) == key ||
                                      (e.hash == hash && key.equals(k))) {  //如果key相等,那么直接替換元素
                                      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)
                                   //如果元素太多了,那么需要重新調(diào)整當(dāng)前的hash結(jié)構(gòu),讓桶變多一些,這樣元素放的更離散一些
                                      rehash(node);
                                  else
                                      setEntryAt(tab, index, node);
                                  ++modCount;
                                  count = c;
                                  oldValue = null;
                                  break;
                              }
                          }
                      } finally {
                          unlock();  //這里必須要在finally里面釋放已經(jīng)獲取的鎖,這樣才能保證鎖一定會被釋放
                      }
                      return oldValue;
                  }





           其實在這里ConcurrentHashMap和HashMap的區(qū)別就已經(jīng)很明顯了:

            (1)ConcurrentHashMap對整個桶數(shù)組進(jìn)行了分段,而HashMap則沒有

            (2)ConcurrentHashMap在每一個分段上都用鎖進(jìn)行保護(hù),從而讓鎖的粒度更精細(xì)一些,并發(fā)性能更好,而HashMap沒有鎖機(jī)制,不是線程安全的。。。

            最后用一張圖來表來說明一下ConcurrentHashMap吧:

            最后,在并發(fā)的情況下,要么使用concurrent類庫中提供的容器,要么就需要自己來管理數(shù)據(jù)的同步問題了。。。

          posted on 2013-09-23 10:48 順其自然EVO 閱讀(5653) 評論(0)  編輯  收藏


          只有注冊用戶登錄后才能發(fā)表評論。


          網(wǎng)站導(dǎo)航:
           
          <2013年9月>
          25262728293031
          1234567
          891011121314
          15161718192021
          22232425262728
          293012345

          導(dǎo)航

          統(tǒng)計

          常用鏈接

          留言簿(55)

          隨筆分類

          隨筆檔案

          文章分類

          文章檔案

          搜索

          最新評論

          閱讀排行榜

          評論排行榜

          主站蜘蛛池模板: 象山县| 苍南县| 惠安县| 永济市| 南城县| 宜章县| 青海省| 海安县| 察雅县| 庐江县| 湛江市| 灌阳县| 博罗县| 沙坪坝区| 祁连县| 定边县| 崇左市| 原阳县| 揭阳市| 木兰县| 黔东| 澄城县| 恩施市| 南溪县| 金山区| 寿阳县| 怀集县| 正蓝旗| 宣武区| 广元市| 朔州市| 万年县| 绥江县| 新乐市| 洪泽县| 含山县| 老河口市| 通海县| 拜城县| 芜湖县| 顺昌县|