原始碼分析–ConcurrentHashMap與HashTable(JDK1.8)

陽光、大地和詩歌發表於2019-01-21

  ConcurrentHashMap和Hashtable都是執行緒安全的K-V型容器。本篇從原始碼入手,簡要說明它們兩者的實現原理和區別。

 

  與HashMap類似,ConcurrentHashMap底層也是以陣列+連結串列+紅黑樹實現的,以Node節點封裝K-V和hash。

static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        volatile V val;
        volatile Node<K,V> next;
}

  val和next以volatile關鍵字進行修飾,保證記憶體可見性。

看一下它的put()方法:

final V putVal(K key, V value, boolean onlyIfAbsent) {
        if (key == null || value == null) throw new NullPointerException();
        int hash = spread(key.hashCode());
        int binCount = 0;
        for (Node<K,V>[] tab = table;;) {
            Node<K,V> f; int n, i, fh;
            if (tab == null || (n = tab.length) == 0)
                tab = initTable();
            else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
                if (casTabAt(tab, i, null,
                             new Node<K,V>(hash, key, value, null)))
                    break;                   // no lock when adding to empty bin
            }
            else if ((fh = f.hash) == MOVED)
                tab = helpTransfer(tab, f);
            else {
                V oldVal = null;
                synchronized (f) {
                    if (tabAt(tab, i) == f) {
                        if (fh >= 0) {
                            binCount = 1;
                            for (Node<K,V> e = f;; ++binCount) {
                                K ek;
                                if (e.hash == hash &&
                                    ((ek = e.key) == key ||
                                     (ek != null && key.equals(ek)))) {
                                    oldVal = e.val;
                                    if (!onlyIfAbsent)
                                        e.val = value;
                                    break;
                                }
                                Node<K,V> pred = e;
                                if ((e = e.next) == null) {
                                    pred.next = new Node<K,V>(hash, key,
                                                              value, null);
                                    break;
                                }
                            }
                        }
                        else if (f instanceof TreeBin) {
                            Node<K,V> p;
                            binCount = 2;
                            if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
                                                           value)) != null) {
                                oldVal = p.val;
                                if (!onlyIfAbsent)
                                    p.val = value;
                            }
                        }
                    }
                }
                if (binCount != 0) {
                    if (binCount >= TREEIFY_THRESHOLD)
                        treeifyBin(tab, i);
                    if (oldVal != null)
                        return oldVal;
                    break;
                }
            }
        }
        addCount(1L, binCount);
        return null;
    }
  • 與HashMap不同,不允許空鍵值
  • 計算hashCode
  • 判斷是否初始化
  • 如果當前位置為空,利用CAS演算法,放置節點
  • 如果當前hashCode == MOVED,進行擴容
  • 利用synchronized鎖,進行連結串列或者紅黑樹的節點放置
  • 連結串列數量大於8,轉為紅黑樹

ConcurrentHashMap的get()方法沒有使用同步鎖機制。

JDK1.8以後,ConcurrentHashMap的執行緒安全都是利用CAS + synchronized來實現的。效率較高。

 

對於HashTable,它底層為陣列+連結串列結構,也不允許空鍵值。以Entry封裝K-V和hash。

主要get()和put()方法:

public synchronized V get(Object key) {
        Entry<?,?> tab[] = table;
        int hash = key.hashCode();
        int index = (hash & 0x7FFFFFFF) % tab.length;
        for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
            if ((e.hash == hash) && e.key.equals(key)) {
                return (V)e.value;
            }
        }
        return null;
    }

public synchronized V put(K key, V value) {
        // Make sure the value is not null
        if (value == null) {
            throw new NullPointerException();
        }

        // Makes sure the key is not already in the hashtable.
        Entry<?,?> tab[] = table;
        int hash = key.hashCode();
        int index = (hash & 0x7FFFFFFF) % tab.length;
        @SuppressWarnings("unchecked")
        Entry<K,V> entry = (Entry<K,V>)tab[index];
        for(; entry != null ; entry = entry.next) {
            if ((entry.hash == hash) && entry.key.equals(key)) {
                V old = entry.value;
                entry.value = value;
                return old;
            }
        }

        addEntry(hash, key, value, index);
        return null;
    }

  HashTable不僅因為沒有紅黑樹,對於資料遍歷的效率就比較低,而且在get()方法都加了synchronized關鍵字,而且get()和put()方法都是直接加在方法上。這樣一來效率就比ConcurrentHashMap低得多了。所以,如果要在併發情況下使用K-V容器,使用ConcurrentHashMap更好。

 

相關文章