qileilove

          blog已經轉移至github,大家請訪問 http://qaseven.github.io/

          java的HashMap與ConcurrentHashMap

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

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

            (2)底層說白了就是以前數據結構課程講過的散列結構。

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

            用一張圖來表示一下散列結構吧:

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

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

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

              public V put(K key, V value) {
                  if (key == null)
                      return putForNullKey(value);
                  int hash = hash(key);  //獲取當前key的hash值
                  int i = indexFor(hash, table.length);  //返回在hash桶里面的位置
                  for (Entry<K,V> e = table[i]; e != null; e = e.next) {  //遍歷當前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;
              }

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

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

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

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

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

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

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

            因此java新的concurrent類庫中就有了ConcurrentHashMap用于在并發環境中使用。。

            那么我們再來看看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  //用于獲取相應的片段
                      s = ensureSegment(j);   //這里表示沒有這個片段,那么需要創建這個片段
                  return s.put(key, hash, value, false);  //這里就有分段加鎖的策略
              }

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

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

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

          final V put(K key, int hash, V value, boolean onlyIfAbsent) {
                   //這里的鎖是計數鎖,同一個鎖可以被同一個線程獲取多次,但是不能被不同的線程獲取
                      HashEntry<K,V> node = tryLock() ? null :   //如果獲取了當前的segment的鎖,那么node為null,待會自己分配就好了
                          scanAndLockForPut(key, hash, value);  //如果沒有加上鎖,那么等吧,有可能的話還要分配entry,反正有時間干嘛不多做一些事情
                      V oldValue;
                      try {
                       //這里表示已經獲取了鎖,那么將在相應的位置放入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;;) {  //從當前的第一個元素開始
                              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)
                                   //如果元素太多了,那么需要重新調整當前的hash結構,讓桶變多一些,這樣元素放的更離散一些
                                      rehash(node);
                                  else
                                      setEntryAt(tab, index, node);
                                  ++modCount;
                                  count = c;
                                  oldValue = null;
                                  break;
                              }
                          }
                      } finally {
                          unlock();  //這里必須要在finally里面釋放已經獲取的鎖,這樣才能保證鎖一定會被釋放
                      }
                      return oldValue;
                  }





           其實在這里ConcurrentHashMap和HashMap的區別就已經很明顯了:

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

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

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

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

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


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


          網站導航:
           
          <2013年9月>
          25262728293031
          1234567
          891011121314
          15161718192021
          22232425262728
          293012345

          導航

          統計

          常用鏈接

          留言簿(55)

          隨筆分類

          隨筆檔案

          文章分類

          文章檔案

          搜索

          最新評論

          閱讀排行榜

          評論排行榜

          主站蜘蛛池模板: 南华县| 安顺市| 石台县| 潜江市| 华阴市| 卢湾区| 历史| 四川省| 安阳县| 望都县| 龙口市| 乌苏市| 巨野县| 永仁县| 江达县| 汝城县| 彭阳县| 迁安市| 阜平县| 根河市| 夹江县| 平昌县| 肇东市| 方山县| 惠州市| 湖州市| 三穗县| 含山县| 扎兰屯市| 海南省| 江门市| 富平县| 安平县| 桓台县| 佛冈县| 新津县| 神木县| 巩留县| 兰坪| 光泽县| 德惠市|