《Java源碼分析》:ConcurrentHashMap JDK1.8__Java

來源:互聯網
上載者:User
《Java源碼分析》:ConcurrentHashMap JDK1.8

最近一直在看關於J.U.C中的源碼,瞭解原子操作,瞭解鎖機制,瞭解多線程並發等等。但是ConcurrentHashMap一直拖著到今天才算告一段落。

也要感謝ConcurrentHashMap這個類,剛開始就是想弄懂裡面的工作原理,但是,無奈看了網上關於介紹ConcurrentHashMap這個類的資料或部落格都是基於JDK1.8以前的,而剛好此類在JDK1.8之後有很大的變化。因此,由於裡面涉及到關於原子操作CAS,自己以前並不知道是什麼,於是就開始對原子操作進行瞭解,看了java.util.concurrent.atom包下相關類源碼對其有了一定的瞭解。接著為了瞭解鎖機制,看了java.util.concurrent.lock包下相關的類庫,對鎖機制有了大概的瞭解之後,看了線程池相關的類,對線程池也有了一定的瞭解。

關於阻塞隊列相關的類,自己也大致看了下,但是並沒有形成相應的博文,以後有時間重新來瞭解他們的時候才記錄吧。整個過程大概花費了我將近一個來月的時間,雖然對看過的類庫的內部實現都只是一個大致的瞭解,但是確實收穫還是挺多的。讓我們更好的明白在多線程並發中他們是如何來工作的。

回到正題,剛好藉著今天星期天,花了將近一天的時間來看ConcurrentHashMap的實現原理,總算看了一個大概,有了一個大致的瞭解。也就有了這篇博文。 ConcurrentHashMap 在JDK1.8版本以前的實現原理

既然本篇博文的標題明確的標出了是基於JDK1.8版本的,也就暗示了這個版本和以前的版本關於ConcurrentHashMap有些許的不同,對吧。x

下面我們就先藉助網上的資料來看下以前版本的ConcurrentHashMap的實現思路。

我們都知道HashMap是線程不安全的。Hashtable是安全執行緒的。看過Hashtable源碼的我們都知道Hashtable的安全執行緒是採用在每個方法來添加了synchronized關鍵字來修飾,即Hashtable是針對整個table的鎖定,這樣就導致HashTable容器在競爭激烈的並發環境下表現出效率低下。

效率低下的原因說的更詳細點:是因為所有訪問HashTable的線程都必須競爭同一把鎖。當一個線程訪問HashTable的同步方法時,其他線程訪問HashTable的同步方法時,可能會進入阻塞或輪詢狀態。如線程1使用put進行添加元素,線程2不但不能使用put方法添加元素,並且也不能使用get方法來擷取元素,所以競爭越激烈效率越低。

基於Hashtable的缺點,人們就開始思考,假如容器裡有多把鎖,每一把鎖用於鎖容器其中一部分資料,那麼當多線程訪問容器裡不同資料區段的資料時,線程間就不會存在鎖競爭,從而可以有效提高並發訪問效率呢。。這就是我們的“鎖分離”技術,這也是ConcurrentHashMap實現的基礎。

ConcurrentHashMap使用的就是鎖分段技術,ConcurrentHashMap由多個Segment組成(Segment下包含很多Node,也就是我們的索引值對了),每個Segment都有把鎖來實現安全執行緒,當一個線程佔用鎖訪問其中一個段資料的時候,其他段的資料也能被其他線程訪問。

因此,關於ConcurrentHashMap就轉化為了對Segment的研究。這是因為,ConcurrentHashMap的get、put操作是直接委託給Segment的get、put方法,但是自己上手上的JDK1.8的具體實現確不想網上這些博文所介紹的。因此,就有了本篇博文的介紹。

推薦幾個JDK1.8以前版本的關於ConcurrentHashMap的原理分析,方便大家比較。

1、http://www.iteye.com/topic/344876

2、http://ifeve.com/concurrenthashmap/

如需要更多,請自己網上搜尋即可。

下面就開始JDK1.8版本中ConcurrentHashMap的介紹。 JDK1.8 版本中ConcurrentHashMap介紹 1、前言

首先要說明的幾點:

1、JDK1.8的ConcurrentHashMap中Segment雖保留,但已經簡化屬性,僅僅是為了相容舊版本。

2、ConcurrentHashMap的底層與Java1.8的HashMap有相通之處,底層依然由“數組”+鏈表+紅/黑樹狀結構來實現的,底層結構存放的是TreeBin對象,而不是TreeNode對象;

3、ConcurrentHashMap實現中借用了較多的CAS演算法,unsafe.compareAndSwapInt(this, valueOffset, expect, update); CAS(Compare And Swap),意思是如果valueOffset位置包含的值與expect值相同,則更新valueOffset位置的值為update,並返回true,否則不更新,返回false。

ConcurrentHashMap既然藉助了CAS來實現非阻塞的無鎖實現安全執行緒,那麼是不是就沒有用鎖了呢。。答案:還是使用了synchronized關鍵字進行同步了的,在哪裡使用了呢。在操作hash值相同的鏈表的頭結點還是會synchronized上鎖,這樣才能保證安全執行緒。

看完ConcurrentHashMap整個類的源碼,給自己的感覺就是和HashMap的實現基本一模一樣,當有修改操作時藉助了synchronized來對table[i]進行鎖定保證了安全執行緒以及使用了CAS來保證原子性操作,其它的基本一致,例如:ConcurrentHashMap的get(int key)方法的實現思路為:根據key的hash值找到其在table所對應的位置i,然後在table[i]位置所儲存的鏈表(或者是樹)進行尋找是否有鍵為key的節點,如果有,則返回節點對應的value,否則返回null。思路是不是很熟悉,是不是和HashMap中該方法的思路一樣。所以,如果你也在看ConcurrentHashMap的源碼,不要害怕,思路還是原來的思路,只是多了些許東西罷了。 2、ConcurrentHashMap類中相關屬性的介紹

為了方便介紹此類後面的實現,這裡需要先將此類中的一些屬性給介紹下。

sizeCtl最重要的屬性之一,看源碼之前,這個屬性工作表示什麼意思,一定要記住。

0、private transient volatile int sizeCtl;//控制標識符

此屬性在源碼中給出的注釋如下:

     /**        * Table initialization and resizing control.  When negative, the        * table is being initialized or resized: -1 for initialization,        * else -(1 + the number of active resizing threads).  Otherwise,        * when table is null, holds the initial table size to use upon        * creation, or 0 for default. After initialization, holds the        * next element count value upon which to resize the table.        */

翻譯如下:

sizeCtl是控制標識符,不同的值表示不同的意義。 負數代表進行中初始化或擴容操作 ,其中-1代表正在初始化 ,-N 表示有N-1個線程進行中擴容操作 正數或0代表hash表還沒有被初始化,這個數值表示初始化或下一次進行擴容的大小,類似於擴容閾值。它的值始終是當前ConcurrentHashMap容量的0.75倍,這與loadfactor是對應的。實際容量>=sizeCtl,則擴容。

1、 transient volatile Node<K,V>[] table;是一個容器數組,第一次插入資料的時候初始化,大小是2的冪次方。這就是我們所說的底層結構:”數組+鏈表(或樹)”

2、private static final int MAXIMUM_CAPACITY = 1 << 30; // 最大容量

3、private static final intDEFAULT_CAPACITY = 16;

4、static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; // MAX_VALUE=2^31-1=2147483647

5、private static finalint DEFAULT_CONCURRENCY_LEVEL = 16;

6、private static final float LOAD_FACTOR = 0.75f;

7、static final int TREEIFY_THRESHOLD = 8; // 鏈錶轉樹的閥值,如果table[i]下面的鏈表長度大於8時就轉化為數

8、static final int UNTREEIFY_THRESHOLD = 6; //樹轉鏈表的閥值,小於等於6是轉為鏈表,僅在擴容tranfer時才可能樹轉鏈表

9、static final int MIN_TREEIFY_CAPACITY = 64;

10、private static final int MIN_TRANSFER_STRIDE = 16;

11、private static int RESIZE_STAMP_BITS = 16;

12、private static final int MAX_RESIZERS = (1 << (32 - RESIZE_STAMP_BITS)) - 1; // help resize的最大線程數

13、private static final int RESIZE_STAMP_SHIFT = 32 - RESIZE_STAMP_BITS;

14、static final int MOVED = -1; // hash for forwarding nodes(forwarding nodes的hash值)、標示位

15、static final int TREEBIN = -2; // hash for roots of trees(樹根節點的hash值)

16、static final int RESERVED = -3; // hash for transient reservations(ReservationNode的hash值) 3、ConcurrentHashMap的建構函式

和往常一樣,我們還是從類的建構函式開始說起。

    /**     * Creates a new, empty map with the default initial table size (16).     */    public ConcurrentHashMap() {    }    public ConcurrentHashMap(int initialCapacity) {        if (initialCapacity < 0)            throw new IllegalArgumentException();        int cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ?                   MAXIMUM_CAPACITY :                   tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1));        this.sizeCtl = cap;    }    /*     * Creates a new map with the same mappings as the given map.     *     */    public ConcurrentHashMap(Map<? extends K, ? extends V> m) {        this.sizeCtl = DEFAULT_CAPACITY;        putAll(m);    }    public ConcurrentHashMap(int initialCapacity, float loadFactor) {        this(initialCapacity, loadFactor, 1);    }    public ConcurrentHashMap(int initialCapacity,                             float loadFactor, int concurrencyLevel) {        if (!(loadFactor > 0.0f) || initialCapacity < 0 || concurrencyLevel <= 0)            throw new IllegalArgumentException();        if (initialCapacity < concurrencyLevel)   // Use at least as many bins            initialCapacity = concurrencyLevel;   // as estimated threads        long size = (long)(1.0 + (long)initialCapacity / loadFactor);        int cap = (size >= (long)MAXIMUM_CAPACITY) ?            MAXIMUM_CAPACITY : tableSizeFor((int)size);        this.sizeCtl = cap;    }

有過HashMap和Hashtable源碼經曆,看這些建構函式是不是相當easy哈。

上面的建構函式主要幹了兩件事:

1、參數的有效性檢查

2、table初始化的長度(如果不指定預設情況下為16)。

這裡要說一個參數:concurrencyLevel,表示能夠同時更新ConccurentHashMap且不產生鎖競爭的最大線程數。預設值為16,(即允許16個線程並發可能不會產生競爭)。為了保證並發的效能,我們要很好的估計出concurrencyLevel值,不然要麼競爭相當厲害,從而導致線程試圖寫入當前鎖定的段時阻塞。 ConcurrentHashMap類中相關節點類:Node/TreeNode/TreeBin 1、Node類

Node類是table數組中的儲存元素,即一個Node對象就代表一個索引值對(key,value)儲存在table中。

Node類是沒有提供修改入口的(唯一的setValue方法拋異常),因此只能用於唯讀遍曆。

此類的具體代碼如下:

    /*     *Node類是沒有提供修改入口的(setValue方法拋異常,供子類實現),     即是可讀的。只能用於唯讀遍曆。     */    static class Node<K,V> implements Map.Entry<K,V> {        final int hash;        final K key;        volatile V val;//volatile,保證可見度        volatile Node<K,V> next;        Node(int hash, K key, V val, Node<K,V> next) {            this.hash = hash;            this.key = key;            this.val = val;            this.next = next;        }        public final K getKey()       { return key; }        public final V getValue()     { return val; }        /*            HashMap中Node類的hashCode()方法中的代碼為:Objects.hashCode(key) ^ Objects.hashCode(value)            而Objects.hashCode(key)最終也是調用了 key.hashCode(),因此,效果一樣。寫法不一樣罷了        */;        public final int hashCode()   { return key.hashCode() ^ val.hashCode(); }        public final String toString(){ return key + "=" + val; }        public final V setValue(V value) { // 不允許修改value值,HashMap允許            throw new UnsupportedOperationException();        }        /*             HashMap使用if (o == this),且嵌套if;ConcurrentHashMap使用&&              個人覺得HashMap格式的代碼更好閱讀和理解        */        public final boolean equals(Object o) {            Object k, v, u; Map.Entry<?,?> e;            return ((o instanceof Map.Entry) &&                    (k = (e = (Map.Entry<?,?>)o).getKey()) != null &&                    (v = e.getValue()) != null &&                    (k == key || k.equals(key)) &&                    (v == (u = val) || v.equals(u)));        }        /*         * Virtualized support for map.get(); overridden in subclasses.         *增加find方法輔助get方法  ,HashMap中的Node類中沒有此方法         */        Node<K,V> find(int h, Object k) {            Node<K,V> e = this;            if (k != null) {                do {                    K ek;                    if (e.hash == h &&                        ((ek = e.key) == k || (ek != null && k.equals(ek))))                        return e;                } while ((e = e.next) != null);            }            return null;        }    }

我們在看這個類時,可以與HashMap中的Node類的具體代碼進行比較,發現在具體的實現上,有一定的細微的區別。

例如:在ConcurrentHashMap.Node的hashCode的代碼是這樣的:

     public final int hashCode()   { return key.hashCode() ^ val.hashCode(); }

而HashMap.Node的hashCode的代碼是這樣的:

    public final int hashCode() {                return Objects.hashCode(key) ^ Objects.hashCode(value);            }

而Objects.hashCode(key)最終也是調用了 key.hashCode(),因此,兩者的效果一樣,寫法不一樣罷了。

除了hashCode方法有一點差別,Node類中的find方法在兩個類的實現中的寫法也不一樣。 2、TreeNode

    /*     * Nodes for use in TreeBins      */    static final class TreeNode<K,V> extends Node<K,V> {        TreeNode<K,V> parent;  // red-black tree links        TreeNode<K,V> left;        TreeNode<K,V> right;        TreeNode<K,V> prev;    // needed to unlink next upon deletion        boolean red;        TreeNode(int hash, K key, V val, Node<K,V> next,                 TreeNode<K,V> parent) {            super(hash, key, val, next);            this.parent = parent;        }        Node<K,V> find(int h, Object k) {            return findTreeNode(h, k, null);        }        /*         * Returns the TreeNode (or null if not found) for the given key         * starting at given root.         *根據給定的key值從root節點出發找出節點         *           */        final TreeNode<K,V> findTreeNode(int h, Object k, Class<?> kc) {            if (k != null) {//HashMap沒有非空判斷                TreeNode<K,V> p = this;                do  {                    int ph, dir; K pk; TreeNode<K,V> q;                    TreeNode<K,V> pl = p.left, pr = p.right;                    if ((ph = p.hash) > h)                        p = pl;                    else if (ph < h)                        p = pr;                    else if ((pk = p.key) == k || (pk != null && k.equals(pk)))                        return p;                    else if (pl == null)                        p = pr;                    else if (pr == null)                        p = pl;                    else if ((kc != null ||                              (kc = comparableClassFor(k)) != null) &&                             (dir = compareComparables(kc, k, pk)) != 0)                        p = (dir < 0) ? pl : pr;                    else if ((q = pr.findTreeNode(h, k, kc)) != null)                        return q;                    else                        p = pl;                } while (p != null);            }            return null;        }    }

和HashMap相比,這裡的TreeNode相當簡潔;ConcurrentHashMap鏈錶轉樹時,並不會直接轉,
正如注釋(Nodes for use in TreeBins)所說,只是把這些節點封裝成TreeNode放到TreeBin中,
再由TreeBin來轉化紅/黑樹狀結構。紅/黑樹狀結構不理解沒關係,並不影響看ConcurrentHashMap的內部實現 3、TreeBins

TreeBin用於封裝維護TreeNode,包含putTreeVal、lookRoot、UNlookRoot、remove、balanceInsetion、balanceDeletion等方法,當鏈錶轉樹時,用於封裝TreeNode,也就是說,ConcurrentHashMap的紅/黑樹狀結構存放的時TreeBin,而不是treeNode。

TreeBins類代碼太長,截取部分代碼如下:

    static final class TreeBin<K,V> extends Node<K,V> {        TreeNode<K,V> root;        volatile TreeNode<K,V> first;        volatile Thread waiter;        volatile int lockState;        // values for lockState        static final int WRITER = 1; // set while holding write lock        static final int WAITER = 2; // set when waiting for write lock        static final int READER = 4; // increment value for setting read lock        /**         * Creates bin with initial set of nodes headed by b.         */        TreeBin(TreeNode<K,V> b) {            super(TREEBIN, null, null, null);            this.first = b;            TreeNode<K,V> r = null;            for (TreeNode<K,V> x = b, next; x != null; x = next) {                next = (TreeNode<K,V>)x.next;                x.left = x.right = null;                if (r == null) {                    x.parent = null;                    x.red = false;                    r = x;                }                else {                    K k = x.key;                    int h = x.hash;                    Class<?> kc = null;                    for (TreeNode<K,V> p = r;;) {                        int dir, ph;                        K pk = p.key;                        if ((ph = p.hash) > h)                            dir = -1;                        else if (ph < h)                            dir = 1;                        else if ((kc == null &&                                  (kc = comparableClassFor(k)) == null) ||                                 (dir = compareComparables(kc, k, pk)) == 0)                            dir = tieBreakOrder(k, pk);                            TreeNode<K,V> xp = p;                        if ((p = (dir <= 0) ? p.left : p.right) == null) {                            x.parent = xp;                            if (dir <= 0)                                xp.left = x;                            else                                xp.right = x;                            r = balanceInsertion(r, x);                            break;                        }                    }                }            }            this.root = r;            assert checkInvariants(root);        }        //........other methods    }
5、ForwardingNode:在transfer操作中,將一個節點插入到桶中
    /*     * A node inserted at head of bins during transfer operations.     *在transfer操作中,一個節點插入到bins中     */    static final class ForwardingNode<K,V> extends Node<K,V> {        final Node<K,V>[] nextTable;        ForwardingNode(Node<K,V>[] tab) {            //Node(int hash, K key, V val, Node<K,V> next)是Node類的建構函式            super(MOVED, null, null, null);            this.nextTable = tab;        }        Node<K,V> find(int h, Object k) {            // loop to avoid arbitrarily deep recursion on forwarding nodes            outer: for (Node<K,V>[] tab = nextTable;;) {                Node<K,V> e; int n;                if (k == null || tab == null || (n = tab.length) == 0 ||                    (e = tabAt(tab, (n - 1) & h)) == null)                    return null;                for (;;) {                    int eh; K ek;                    if ((eh = e.hash) == h &&                        ((ek = e.key) == k || (ek != null && k.equals(ek))))                        return e;                    if (eh < 0) {                        if (e instanceof ForwardingNode) {                            tab = ((ForwardingNode<K,V>)e).nextTable;                            continue outer;                        }                        else                            return e.find(h, k);                    }                    if ((e = e.next) == null)                        return null;                }            }        }    }
ConcurrentHashMap類中的put(K key, V value)方法的原理分析

我們對Node、TreeNode、TreeBin有一點認識後,我們就可以看下ConcurrentHashMap類的put方法是如何來實現的了,這裡給出一個建議,關於容器我們用的最多的就是put、get方法了,我們看源碼的實現,我們核心要關注的就是put、get方法的實現,只要我們弄懂這兩個方法實現,這個類的大概實現思想我們也就知道了哈

基於此,我們就先來看ConcurrentHashMap類的put方法

put(K key, V value)方法的功能:將制定的索引值對映射到table中,key/value均不能為null

put方法的代碼如下:

    public V put(K key, V value) {        return putVal(key, value, false);    }

由於直接是調用了putVal(key, value, false)方法,那就我們就繼續看。

putVal(key, value, false)方法的代碼如下:

    /** Implementation for put and putIfAbsent */    final V putVal(K key, V value, boolean onlyIfAbsent) {        if (key == null || value == null) throw new NullPointerException();        int hash = spread(key.hashCode());//計算hash值,兩次hash操作        int binCount = 0;        for (Node<K,V>[] tab = table;;) {//類似於while(true),死迴圈,直到插入成功             Node<K,V> f; int n, i, fh;            if (tab == null || (n = tab.length) == 0)//檢查是否初始化了,如果沒有,則初始化                tab = initTable();                /*                    i=(n-1)&hash 等價於i=hash%n(前提是n為2的冪次方).即取出table中位置的節點用f表示。                    有如下兩種情況:                    1、如果table[i]==null(即該位置的節點為空白,沒有發生碰撞),則利用CAS操作直接儲存在該位置,                        如果CAS操作成功則退出死迴圈。                    2、如果table[i]!=null(即該位置已經有其它節點,發生碰撞)                */            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)//檢查table[i]的節點的hash是否等於MOVED,如果等於,則檢測到正在擴容,則協助其擴容                tab = helpTransfer(tab, f);//協助其擴容            else {//運行到這裡,說明table[i]的節點的hash值不等於MOVED。                V oldVal = null;                synchronized (f) {//鎖定,(hash值相同的鏈表的前端節點)                    if (tabAt(tab, i) == f) {//避免多線程,需要重新檢查                        if (fh >= 0) {//鏈表節點                            binCount = 1;                            /*                            下面的代碼就是先尋找鏈表中是否出現了此key,如果出現,則更新value,並跳出迴圈,                            否則將節點加入到裡阿尼報末尾並跳出迴圈                            */                            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)//僅putIfAbsent()方法中onlyIfAbsent為true

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.