ConcurrentHashMap解析一

首先介紹一下ConcurrentHashMap的成員變量和常量

Constants

    private static final int MAXIMUM_CAPACITY = 1 << 30; //最大容量2的30次方
    private static final int DEFAULT_CAPACITY = 16; //默認容量  1<<4
    
    private static final float LOAD_FACTOR = 0.75f;  //負載因子
    static final int TREEIFY_THRESHOLD = 8;  //鏈表轉為紅黑樹,大于8小于6先對鏈表數組進行翻倍擴容操作
    static final int UNTREEIFY_THRESHOLD = 6;  //樹轉列表
    static final int MIN_TREEIFY_CAPACITY = 64; //鏈表真正轉為紅黑樹
    private static final int MIN_TRANSFER_STRIDE = 16;
    private static int RESIZE_STAMP_BITS = 16;//stamp高位標識移動位數
    private static final int MAX_RESIZERS = (1 << (32 - RESIZE_STAMP_BITS)) - 1;
    private static final int RESIZE_STAMP_SHIFT = 32 - RESIZE_STAMP_BITS;
    static final int MOVED     = -1; // forwarding nodes 的hash值,如果hash值等于-1代表線程協助擴容
    static final int TREEBIN   = -2; // roots of trees 的hash值,如果hash等于-2代表,當前桶是紅黑樹
    static final int RESERVED  = -3; // transient reservations 的hash值

    // usable bits of normal node hash,在hash計算的時候運用到,與HashMap計算出來的hash值進行與操作
    static final int HASH_BITS = 0x7fffffff; 
    
    static final int NCPU = Runtime.getRuntime().availableProcessors(); //可用處理器數量       

Fields

transient volatile Node<K,V>[] table;//當前ConcurrentHashmap的Node數組

private transient volatile Node<K,V>[] nextTable;//ForwardNode所指向的下一個表


private transient volatile long baseCount;//如果使用CAS計數成功,使用該值進行累加

//擴容設置的參數,默認為0,當值=-1的時候,代表當前有線程正在進行擴容操作
//當值等于-n的時候,代表有n個線程一起擴容,其中n-1線程是協助擴容
//當在初始化的時候指定了大小,這會將這個大小保存在sizeCtl中,大小為數組的0.75
private transient volatile int sizeCtl;


private transient volatile int transferIndex;


private transient volatile int cellsBusy;

//如果使用CAS計算失敗,也就是說當前處于高并發的情況下,那么
//就會使用CounterCell[]數組進行計數,類似jdk1.7分段鎖的形式,鎖住一個segment
//最后size()方法統計出來的大小是baseCount和counterCells數組的總和
private transient volatile CounterCell[] counterCells;

然后我們介紹一下ConcurrentHashMap為我們提供的三個核心的原子方法:

     //獲取當前地址偏移量的Node對象
    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);
    }
//對比當前地址偏移量的對象和第三個參數的對象是否為同一個對象
    //一樣則將這個對象替換成第四個參數的對象并且返回true
    //不一樣則直接返回一個false
    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);
    }

三個方法的作用都在代碼上注釋了,三個方法中調用的方法都是native類型的無法直接看到源碼,那么我們分析一下,他是怎么獲取到地址偏移量的,在代碼中我們看到了他進行了這樣的轉換
((long)i << ASHIFT) + ABASE
這條代碼中有兩個變量,我解釋一些這兩個變量
ABASE:是針對當前數組首個元素的地址偏移量
ASHIFT:是針對當前數組某個位置上元素所占字節并且經過相對應的處理而產生的結果,下面會重點講解一下這個

首先是ABASE變量的獲取:
ABASE = U.arrayBaseOffset(對象);
U是Unsafe的實例對象,這個對象不能輕易獲取到,不過可以通過反射來獲取,示例:

    try {
        Field f = Unsafe.class.getDeclaredField("theUnsafe");
        f.setAccessible(true);
        U = (Unsafe) f.get(null);
        int scare = U.arrayIndexScale(String[].class);
        System.out.println(scare);
    } catch (Exception e) {
        e.printStackTrace();
    }

在ConcurrentHashMap中,傳入這個方法的對象其實不是對象本身,而是對象的一個引用,所以我們來看下ASHIFT變量獲取的方法
首先通過

      int scale = U.arrayIndexScale(ak);

來進行獲取當前數組元素所占字節,因為是對象引用,所以都是返回4
然后再進行第二步的處理:

    ASHIFT = 31 - Integer.numberOfLeadingZeros(scale);

這個地方才是真的為ASHIFT進行賦值的地方,后一個方法返回的是參數的二進制代碼左側包含0的個數,在當前實例中,參數為4,二進制表示為100,前面含有29個零,所以這個變量被賦值為2
這樣我們就可以總結一下,地址偏移量 = (傳入值 << ASHIFT) + ABASE
這三個方法頻繁的在CAS算法中使用,所以要先了解一下。

下面分析一下ConcurrentHashMap的構造方法,這個類有五個構造方法分別是
1、public ConcurrentHashMap()//無參構造方法
2、public ConcurrentHashMap(int initialCapacity)//參數為容器容量
3、public ConcurrentHashMap(Map<? extends K, ? extends V> m)//參數為傳入的map集合
4、public ConcurrentHashMap(int initialCapacity, float loadFactor)//參數1容量,參數2負載因子
5、public ConcurrentHashMap(int initialCapacity,float loadFactor, int concurrencyLevel)
//第一個參數容量,第二個參數負載因子,第三個參數并發級別,用來確定segment的

這里我主要拿一個構造方法出來分析:

    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;

    }

這個構造方法,我們可以看出,如果你傳入的參數是容量大小,那么他在進行向上找2倍數最近值的時候,他傳入tableSizeFor()方法的參數是之前傳入容量的1.5倍+1,這個地方在后頭鏈表超過長度8開始轉換的時候也有同樣的操作,也就是說,擴容是按照兩倍來擴容的,最后將獲得的值cap賦值給sizeCTL,這邊也進一步說明了,sizeCTL為正代表的是容量的大小。

參考鏈接:
https://blog.csdn.net/u012129558/article/details/52268194
https://blog.csdn.net/u010723709/article/details/48007881
http://www.lxweimin.com/p/9819eb48716a
http://www.lxweimin.com/p/c17ca559886d

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 一、基本數據類型 注釋 單行注釋:// 區域注釋:/* */ 文檔注釋:/** */ 數值 對于byte類型而言...
    龍貓小爺閱讀 4,288評論 0 16
  • 在一個方法內部定義的變量都存儲在棧中,當這個函數運行結束后,其對應的棧就會被回收,此時,在其方法體中定義的變量將不...
    Y了個J閱讀 4,436評論 1 14
  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,868評論 18 139
  • 在這里,我們是最傷感的孩子, 等秋風,等你 ,以及金色的灰塵。 我們,來自最久遠的春天, 最安靜的田園,來自 一場...
    forgetmefish閱讀 691評論 9 18
  • 吃完了泡面,碗還在桌子上放著,水杯里泡了一天的菊花在杯底團成一坨,盤子里堆了一半瓜子和一半瓜子皮,我仰在床上,刷著...
    陽小傻閱讀 271評論 2 4