1. 程式人生 > >多執行緒環境下的ConcurrentHashMap

多執行緒環境下的ConcurrentHashMap

文章目錄

什麼是ConcurrentHashMap?

ConcurrentHashMap並像HashMap一樣在java.util包下,而是在java.util.concurrent包下,可見ConcurrentHashMap是為併發而存在的。ConcurrentHashMap是高併發環境下使用的HashMap,通過volatile、CAS、synchronized等來實現了執行緒安全的HashMap。

底層資料結構?

由於取消了segment,Java1.8中的ConcurrentHashMap跟HashMap的資料結構類似,參見上一篇HashMap。
Java1.8中引入了TreeBin來維護對桶內TreeNode的引用以及鎖狀態。

如何實現併發安全?

Java1.8中主要是通過CAS和synchronized來實現多執行緒安全的。
對於CAS有三個Table element access方法:

    @SuppressWarnings("unchecked")
    static final <K,V> Node<K,V> tabAt(Node<
K,V>
[] tab, int i) { return (Node<K,V>)U.getObjectVolatile(tab, ((long)i << ASHIFT) + ABASE); } static final <K,V> boolean casTabAt(Node<K,V>[] tab, int i, Node<K,V> c, Node<K,V> v) { return U.compareAndSwapObject
(tab, ((long)i << ASHIFT) + ABASE, c, v); } static final <K,V> void setTabAt(Node<K,V>[] tab, int i, Node<K,V> v) { U.putObjectVolatile(tab, ((long)i << ASHIFT) + ABASE, v); }

put操作和HashMap一樣還是使用putVal函式實現,不同的是,在當前位置無元素時,使用CAS來新增Node結點,如果失敗,則繼續迴圈,迴圈條件是
for (Node<K,V>[] tab = table;;)
直到CAS成功才break。
如果當前位置有元素,則對當前結點即頭結點使用synchronized,縮小了鎖的粒度。
get操作並沒有加鎖,並不能完全保證多執行緒的一致性。
size操作返回的也不是一個精確值。

    public int size() {
        long n = sumCount();
        return ((n < 0L) ? 0 :
                (n > (long)Integer.MAX_VALUE) ? Integer.MAX_VALUE :
                (int)n);
    }
    final long sumCount() {
        CounterCell[] as = counterCells; CounterCell a;
        long sum = baseCount;
        if (as != null) {
            for (int i = 0; i < as.length; ++i) {
                if ((a = as[i]) != null)
                    sum += a.value;
            }
        }
        return sum;
    }

transfer和ForwardingNode

transfer(Node<K,V>[] tab, Node<K,V>[] nextTab)是在擴容時使用的函式,它會將舊桶上的結點拷貝到新桶(Moves and/or copies the nodes in each bin to new table)。在結點中有一類Special Nodes。其中在transfer操作時插入到桶的頭結點的特殊結點叫做ForwardingNode(A node inserted at head of bins during transfer operations)。可以把該結點稱為轉發結點,因為在擴容時,對舊table的訪問,會有該結點轉發到新table上。建構函式為:

 ForwardingNode(Node<K,V>[] tab) {
            super(MOVED, null, null, null);
            this.nextTable = tab;
 }
ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab);

在transfer操作時,會呼叫此操作,

Hash計算

put時的hash計算藉助了spread函式

    int hash = spread(key.hashCode());
    static final int spread(int h) {
        return (h ^ (h >>> 16)) & HASH_BITS;//HASH_BITS = 0x7fffffff;
    }