1. 程式人生 > >java8 ConcurrentHashMap原始碼解析

java8 ConcurrentHashMap原始碼解析

浪費了“黃金五年”的Java程式設計師,還有救嗎? >>>   

Java8較java7的改進:

改進一:取消segments欄位,直接採用transient volatile HashEntry<K,V> table儲存資料,採用table陣列元素作為鎖,從而實現了對每一行資料進行加鎖,進一步減少併發衝突的概率。

/**
 * The array of bins. Lazily initialized upon first insertion.
 * Size is always a power of two(2的冪). Accessed directly by iterators.
 */
transient volatile Node<K,V>[] table;

 

改進二:將原先table陣列+單向連結串列的資料結構,變更為table陣列+單向連結串列+紅黑樹的結構。對於hash表來說,最核心的能力在於將key hash之後能均勻的分佈在陣列中。如果hash之後雜湊的很均勻,那麼table陣列中的每個佇列長度主要為0或者1。但實際情況並非總是如此理想,雖然ConcurrentHashMap類預設的載入因子為0.75,但是在資料量過大或者運氣不佳的情況下,還是會存在一些佇列長度過長的情況,如果還是採用單向列表方式,那麼查詢某個節點的時間複雜度為O(n);因此,對於個數超過8(預設值)的列表,jdk1.8中採用了紅黑樹的結構,那麼查詢的時間複雜度可以降低到O(logN),可以改進效能。

重要屬性:

/**

 * 這個sizeCtl是volatile的,那麼他是執行緒可見的,一個思考:它是所有修改都在CAS中進行,但是sizeCtl為什麼不設計成LongAdder(jdk8出現的)型別呢?

 * 或者設計成AtomicLong(在高併發的情況下比LongAdder低效),這樣就能減少自己操作CAS了。

 *

 * 預設為0,用來控制table的初始化和擴容操作,具體應用在後續會體現出來。

 * -1 代表table正在初始化

 * -N 表示有N-1個執行緒正在進行擴容操作

 * 其餘情況:

 *1、如果table未初始化,表示table需要初始化的大小。

 *2、如果table初始化完成,表示table的容量,預設是table大小的0.75 倍,居然用這個公式算0.75(n - (n >>> 2))。

 **/

private static final long SIZECTL;

private static final long TRANSFERINDEX;

/**

 * races. Updated via CAS.

 * 記錄容器的容量大小,通過CAS更新

 */

private static final long BASECOUNT;

/**

 *  自旋鎖 (鎖定通過 CAS) 在調整大小和/或建立 CounterCells 時使用。 在CounterCell類更新value中會使用,功能類似顯示鎖和內建鎖,效能更好

 *  在Striped64類也有應用

 */

private static final long CELLSBUSY;

private static final long CELLVALUE;

private static final long ABASE;

private static final int ASHIFT;
/**

 * Node:儲存key,value及key的hash值的資料結構。其中value和next都用volatile修飾,保證併發的可見性。

 * @param <K>

 * @param <V>

 */

static class Node<K,V> implements Entry<K,V> {

    final int hash;

    final K key;

    volatile V val;



..    volatile Node<K,V> next;
}
/**

 * ForwardingNode:一個特殊的Node節點,hash值為-1,其中儲存nextTable的引用。

 * @param <K>

 * @param <V>

 */

static final class ForwardingNode<K,V> extends Node<K,V> {

    final Node<K,V>[] nextTable;
…
}

建構函式:

/**
 *initialCapacity 初始化容量
 **/
public ConcurrentHashMap(int initialCapacity) 
/**
 *
 *建立與給定map具有相同對映的新map
 **/
public ConcurrentHashMap(Map<? extends K, ? extends V> m) 
/**
 *initialCapacity 初始容量
 *loadFactor 負載因子,當容量達到initialCapacity*loadFactor時,執行擴容
 **/
public ConcurrentHashMap(int initialCapacity, float loadFactor)
/**
 *initialCapacity 初始容量
 *loadFactor 負載因子
 *concurrencyLevel 預估的併發更新執行緒數
 **/

public ConcurrentHashMap(int initialCapacity,float loadFactor, int concurrencyLevel)

 

方法:

put:

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


final V putVal(K key, V value, boolean onlyIfAbsent) {
    if (key == null || value == null) throw new NullPointerException();
    int hash = spread(key.hashCode());//對hashCode進行再雜湊,演算法為(h ^ (h >>> 16)) & HASH_BITS
    int binCount = 0;
    //這邊加了一個迴圈,就是不斷的嘗試,因為在table的初始化和casTabAt用到了compareAndSwapInt、compareAndSwapObject
    //因為如果其他執行緒正在修改tab,那麼嘗試就會失敗,所以這邊要加一個for迴圈,不斷的嘗試
    for (Node<K,V>[] tab = table;;) {
        Node<K,V> f; int n, i, fh;
        // 如果table為空,初始化;否則,根據hash值計算得到陣列索引i,如果tab[i]為空,直接新建節點Node即可。注:tab[i]實質為連結串列或者紅黑樹的首節點。
        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
        }
        // 如果tab[i]不為空並且hash值為MOVED(-1),說明該連結串列正在進行transfer操作,返回擴容完成後的table
        else if ((fh = f.hash) == MOVED)
            tab = helpTransfer(tab, f);
        else {
            V oldVal = null;
            // 針對首個節點進行加鎖操作,而不是segment,進一步減少執行緒衝突
            synchronized (f) {
                if (tabAt(tab, i) == f) {
                    if (fh >= 0) {
                        binCount = 1;
                        for (Node<K,V> e = f;; ++binCount) {
                            K ek;
                            // 如果在連結串列中找到值為key的節點e,直接設定e.val = value即可。
                            if (e.hash == hash &&
                                ((ek = e.key) == key ||
                                 (ek != null && key.equals(ek)))) {
                                oldVal = e.val;
                                if (!onlyIfAbsent)
                                    e.val = value;
                                break;

                            }

                            // 如果沒有找到值為key的節點,直接新建Node並加入連結串列即可。

                            Node<K,V> pred = e;

                            if ((e = e.next) == null) {

                                pred.next = new Node<K,V>(hash, key,

                                                          value, null);

                                break;

                            }

                        }

                    }

                    // 如果首節點為TreeBin型別,說明為紅黑樹結構,執行putTreeVal操作。

                    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) {
                // 如果節點數>=8,那麼轉換連結串列結構為紅黑樹結構。
                if (binCount >= TREEIFY_THRESHOLD)
                    treeifyBin(tab, i);
                if (oldVal != null)
                    return oldVal;
                break;
            }
        }
    }
    // 計數增加1,有可能觸發transfer操作(擴容)。
    addCount(1L, binCount);
    return null;

}

helpTransfer:

final Node<K,V>[] helpTransfer(Node<K,V>[] tab, Node<K,V> f) {

    Node<K,V>[] nextTab; int sc;

    if (tab != null && (f instanceof ForwardingNode) &&

        (nextTab = ((ForwardingNode<K,V>)f).nextTable) != null) {

        int rs = resizeStamp(tab.length);

        while (nextTab == nextTable && table == tab &&

               (sc = sizeCtl) < 0) {

            //下面幾種情況和addCount的方法一樣,請參考addCount的備註

            if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||

                sc == rs + MAX_RESIZERS || transferIndex <= 0)

                break;

            if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) {

                transfer(tab, nextTab);

                break;

            }

        }

        return nextTab;

    }

    return table;

}

tabAt:

@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);

}





/*

 *但是這邊為什麼i要等於((long)i << ASHIFT) + ABASE呢,計算偏移量

 *ASHIFT是指tab[i]中第i個元素在相對於陣列第一個元素的偏移量,而ABASE就算第一陣列的記憶體素的偏移地址

 *所以呢,((long)i << ASHIFT) + ABASE就算i最後的地址

 * 那麼compareAndSwapObject的作用就算tab[i]和c比較,如果相等就tab[i]=v否則tab[i]=c;

 */

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);

}

addCount:

private final void addCount(long x, int check) {

    CounterCell[] as; long b, s;

    //U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x) 每次進來都baseCount都加1因為x=1

    if ((as = counterCells) != null ||

        !U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) {//1

        CounterCell a; long v; int m;

        boolean uncontended = true;

        if (as == null || (m = as.length - 1) < 0 ||

            (a = as[ThreadLocalRandom.getProbe() & m]) == null ||

            !(uncontended =

              U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) {

            //多執行緒CAS發生失敗的時候執行

            fullAddCount(x, uncontended);//2

            return;

        }

        if (check <= 1)

            return;

        s = sumCount();

    }

    if (check >= 0) {

        Node<K,V>[] tab, nt; int n, sc;

        //當條件滿足開始擴容

        while (s >= (long)(sc = sizeCtl) && (tab = table) != null &&

               (n = tab.length) < MAXIMUM_CAPACITY) {

            int rs = resizeStamp(n);

            if (sc < 0) {//如果小於0說明已經有執行緒在進行擴容操作了

                //一下的情況說明已經有在擴容或者多執行緒進行了擴容,其他執行緒直接break不要進入擴容操作

                if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||

                    sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||

                    transferIndex <= 0)

                    break;

                if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))//如果相等說明擴容已經完成,可以繼續擴容

                    transfer(tab, nt);

            }

            //這個時候sizeCtl已經等於(rs << RESIZE_STAMP_SHIFT) + 2等於一個大的負數,

            // 這邊加上2很巧妙,因為transfer後面對sizeCtl--操作的時候,最多隻能減兩次就結束

            else if (U.compareAndSwapInt(this, SIZECTL, sc,

                                         (rs << RESIZE_STAMP_SHIFT) + 2))

                transfer(tab, null);

            s = sumCount();

        }

    }

}

上面註釋1,每次都會對baseCount 加1,如果併發競爭太大,那麼可能導致U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x) 失敗,那麼為了提高高併發的時候baseCount可見性失敗的問題,又避免一直重試,這樣效能會有很大的影響,那麼在jdk8的時候是有引入一個類Striped64,其中LongAdder和DoubleAdder就是對這個類的實現。這兩個方法都是為解決高併發場景而生的,是AtomicLong的加強版,AtomicLong在高併發場景效能會比LongAdder差。但是LongAdder的空間複雜度會高點。

 

我們每次進來都對baseCount進行加1當達到一定的容量時,就需要對table進行擴容。擴容方法就是transfer

/**

 * Moves and/or copies the nodes in each bin to new table. See

 * above for explanation.

 */

private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {

    int n = tab.length, stride;

    if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)

        stride = MIN_TRANSFER_STRIDE; // subdivide range

    if (nextTab == null) {            // initiating

        try {

            @SuppressWarnings("unchecked")

            Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n << 1];

            nextTab = nt;

        } catch (Throwable ex) {      // try to cope with OOME

            sizeCtl = Integer.MAX_VALUE;

            return;

        }

        nextTable = nextTab;

        transferIndex = n;

    }

    int nextn = nextTab.length;

    //構建一個連節點的指標,用於標識位

    ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab);

    boolean advance = true;

    boolean finishing = false; // to ensure sweep before committing nextTab

    //迴圈的關鍵變數,判斷是否已經擴容完成,完成就return,退出迴圈

    for (int i = 0, bound = 0;;) {

        Node<K,V> f; int fh;

        //迴圈的關鍵i,i--操作保證了倒序遍歷陣列

        while (advance) {

            int nextIndex, nextBound;

            if (--i >= bound || finishing)

                advance = false;

            else if ((nextIndex = transferIndex) <= 0) {//nextIndex=transferIndex=n=tab.length(預設16)

                i = -1;

                advance = false;

            }

            else if (U.compareAndSwapInt

                     (this, TRANSFERINDEX, nextIndex,

                      nextBound = (nextIndex > stride ?

                                   nextIndex - stride : 0))) {

                bound = nextBound;

                i = nextIndex - 1;

                advance = false;

            }

        }

        //i<0說明已經遍歷完舊的陣列tab;i>=n什麼時候有可能呢?在下面看到i=n,所以目前i最大應該是n吧。

        //i+n>=nextn,nextn=nextTab.length,所以如果滿足i+n>=nextn說明已經擴容完成

        if (i < 0 || i >= n || i + n >= nextn) {

            int sc;

            if (finishing) {// a

                nextTable = null;

                table = nextTab;

                sizeCtl = (n << 1) - (n >>> 1);

                return;

            }

            //利用CAS方法更新這個擴容閾值,在這裡面sizectl值減一,說明新加入一個執行緒參與到擴容操作,參考sizeCtl的註釋

            if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) {

                //如果有多個執行緒進行擴容,那麼這個值在第二個執行緒以後就不會相等,因為sizeCtl已經被減1了,

                // 所以後面的執行緒就只能直接返回,始終保證只有一個執行緒執行了 a(上面註釋a)

                if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT)

                    return;

                finishing = advance = true;//finishing和advance保證執行緒已經擴容完成了可以退出迴圈

                i = n; // recheck before commit

            }

        }

        else if ((f = tabAt(tab, i)) == null)//如果tab[i]為null,那麼就把fwd插入到tab[i],表明這個節點已經處理過了

            advance = casTabAt(tab, i, null, fwd);

        else if ((fh = f.hash) == MOVED)//那麼如果f.hash=-1的話說明該節點為ForwardingNode,說明該節點已經處理過了

            advance = true; // already processed

        else {

            synchronized (f) {

                if (tabAt(tab, i) == f) {

                    Node<K,V> ln, hn;

                    if (fh >= 0) {

                        int runBit = fh & n;

                        Node<K,V> lastRun = f;

                        //這邊還對連結串列進行遍歷,這邊的的演算法和hashmap的演算法又不一樣了,這班是有點對半拆分的感覺

                        //把連結串列分表拆分為,hash&n等於0和不等於0的,然後分別放在新表的i和i+n位置

                        //次方法同hashmap的resize

                        for (Node<K,V> p = f.next; p != null; p = p.next) {

                            int b = p.hash & n;

                            if (b != runBit) {

                                runBit = b;

                                lastRun = p;

                            }

                        }

                        if (runBit == 0) {

                            ln = lastRun;

                            hn = null;

                        }

                        else {

                            hn = lastRun;

                            ln = null;

                        }

                        for (Node<K,V> p = f; p != lastRun; p = p.next) {

                            int ph = p.hash; K pk = p.key; V pv = p.val;

                            if ((ph & n) == 0)

                                ln = new Node<K,V>(ph, pk, pv, ln);

                            else

                                hn = new Node<K,V>(ph, pk, pv, hn);

                        }

                        setTabAt(nextTab, i, ln);

                        setTabAt(nextTab, i + n, hn);

                        //把已經替換的節點的舊tab的i的位置用fwd替換,fwd包含nextTab

                        setTabAt(tab, i, fwd);

                        advance = true;

                    }//下面紅黑樹基本和連結串列差不多

                    else if (f instanceof TreeBin) {

                        TreeBin<K,V> t = (TreeBin<K,V>)f;

                        TreeNode<K,V> lo = null, loTail = null;

                        TreeNode<K,V> hi = null, hiTail = null;

                        int lc = 0, hc = 0;

                        for (Node<K,V> e = t.first; e != null; e = e.next) {

                            int h = e.hash;

                            TreeNode<K,V> p = new TreeNode<K,V>

                                (h, e.key, e.val, null, null);

                            if ((h & n) == 0) {

                                if ((p.prev = loTail) == null)

                                    lo = p;

                                else

                                    loTail.next = p;

                                loTail = p;

                                ++lc;

                            }

                            else {

                                if ((p.prev = hiTail) == null)

                                    hi = p;

                                else

                                    hiTail.next = p;

                                hiTail = p;

                                ++hc;

                            }

                        }

                        //判斷擴容後是否還需要紅黑樹結構

                        ln = (lc <= UNTREEIFY_THRESHOLD) ? untreeify(lo) :

                            (hc != 0) ? new TreeBin<K,V>(lo) : t;

                        hn = (hc <= UNTREEIFY_THRESHOLD) ? untreeify(hi) :

                            (lc != 0) ? new TreeBin<K,V>(hi) : t;

                        setTabAt(nextTab, i, ln);

                        setTabAt(nextTab, i + n, hn);

                        setTabAt(tab, i, fwd);

                        advance = true;

                    }

                }

            }

        }

    }

}

注意:如果連結串列結構中元素超過TREEIFY_THRESHOLD閾值,預設為8個,則把連結串列轉化為紅黑樹,提高遍歷查詢效率.接下來我們看看如何構造樹結構,程式碼如下:

private final void treeifyBin(Node<K,V>[] tab, int index) {

    Node<K,V> b; int n, sc;

    if (tab != null) {

        if ((n = tab.length) < MIN_TREEIFY_CAPACITY)

            tryPresize(n << 1);

        else if ((b = tabAt(tab, index)) != null && b.hash >= 0) {

            synchronized (b) {

                if (tabAt(tab, index) == b) {

                    TreeNode<K,V> hd = null, tl = null;

                    for (Node<K,V> e = b; e != null; e = e.next) {

                        TreeNode<K,V> p =

                            new TreeNode<K,V>(e.hash, e.key, e.val,

                                              null, null);

                        if ((p.prev = tl) == null)

                            hd = p;

                        else

                            tl.next = p;

                        tl = p;

                    }

                    setTabAt(tab, index, new TreeBin<K,V>(hd));

                }

            }

        }

    }

}

可以看出,生成樹節點的程式碼塊是同步的,進入同步程式碼塊之後,再次驗證table中index位置元素是否被修改過。

1、根據table中index位置Node連結串列,重新生成一個hd為頭結點的TreeNode連結串列。

2、根據hd頭結點,生成TreeBin樹結構,並把樹結構的root節點寫到table的index位置的記憶體中,具體實現如下:

/**

 * 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);

}

Get:

public V get(Object key) {

    Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;

    int h = spread(key.hashCode());

    if ((tab = table) != null && (n = tab.length) > 0 &&

        (e = tabAt(tab, (n - 1) & h)) != null) {

        if ((eh = e.hash) == h) {

            if ((ek = e.key) == key || (ek != null && key.equals(ek)))

                return e.val;

        }

        else if (eh < 0)//如果eh=-1就說明e節點為ForWordingNode,這說明什麼,說明這個節點已經不存在了,被另一個執行緒正則擴容

            //所以要查詢key對應的值的話,直接到新newtable找

            return (p = e.find(h, key)) != null ? p.val : null;

        while ((e = e.next) != null) {

            if (e.hash == h &&

                ((ek = e.key) == key || (ek != null && key.equals(ek))))

                return e.val;

        }

    }

    return null;

}

這個get請求,我們需要cas來保證變數的原子性。如果tab[i]正被鎖住,那麼CAS就會失敗,失敗之後就會不斷的重試。這也保證了get在高併發情況下不會出錯。

我們來分析下到底有多少種情況會導致get在併發的情況下可能取不到值。1、一個執行緒在get的時候,另一個執行緒在對同一個key的node進行remove操作;2、一個執行緒在get的時候,另一個執行緒正則重排table。可能導致舊table取不到值。

那麼本質是,我在get的時候,有其他執行緒在對同一桶的連結串列或樹進行修改。那麼get是怎麼保證同步性的呢?我們看到e = tabAt(tab, (n - 1) & h)) != null,在看下tablAt到底是幹嘛的:

 

@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);

}


它是對tab[i]進行原子性的讀取,因為我們知道putVal等對table的桶操作是有加鎖的,那麼一般情況下我們對桶的讀也是要加鎖的,但是我們這邊為什麼不需要加鎖呢?因為我們用了Unsafe的getObjectVolatile,因為table是volatile型別,所以對tab[i]的原子請求也是可見的。因為如果同步正確的情況下,根據happens-before原則,對volatile域的寫入操作happens-before於每一個後續對同一域的讀操作。所以不管其他執行緒對table連結串列或樹的修改,都對get讀取可見。

 

sun.misc.Unsafe類:

Unsafe類是什麼呢?java不能直接訪問作業系統底層,而是通過本地方法來訪問。Unsafe類提供了硬體級別的原子操作。Unsafe類在jdk 原始碼的多個類中用到,這個類的提供了一些繞開JVM的更底層功能,基於它的實現可以提高效率。但是,它是一把雙刃劍:正如它的名字所預示的那樣,它是Unsafe的,它所分配的記憶體需要手動free(不被GC回收)。Unsafe類,提供了JNI某些功能的簡單替代:確保高效性的同時,使事情變得更簡單。

//在o的offset偏移地址處,獲取volatile型別的物件
public native java.lang.Object getObjectVolatile(java.lang.Object o, long l);

//原子性的更新java變數
public final native boolean compareAndSwapObject(java.lang.Object o, long l, java.lang.Object o1, java.lang.Object o2);

/**
     * Stores a reference value into a given Java variable, with volatile store
     * semantics. Otherwise identical to
     * {@link #putObject(Object, long, Object)}
     */
public native void putObjectVolatile(java.lang.Object o, long l, java.lang.Object o1);

  /**
     * Atomically update Java variable to <tt>x</tt> if it is currently holding
     * <tt>expected</tt>.
     * 
     * @return <tt>true</tt> if successful
     */
public final native boolean compareAndSwapLong(java.lang.Object o, long l, long l1, long l2);
 

相關推薦

java8 ConcurrentHashMap原始碼解析

浪費了“黃金五年”的Java程式設計師,還有救嗎? >>>   

併發程式設計---ConcurrentHashMap原始碼解析

    ConcurrentHashMap是java中為了解決HashMap不能支援高併發而設計的新的實現。     ConcurrentHashMap的類結構 public class ConcurrentHashMap<K,V> ext

ConcurrentHashMap原始碼解析(JDK1.8)

package java.util.concurrent; import java.io.ObjectStreamField; import java.io.Serializable; import java.lang.reflect.ParameterizedType;

Java8 HashMap原始碼解析

前言 Java7中的HashMap和Java8中的HashMap不太一樣,Java7中的HashMap主要是由陣列+連結串列組成的,而Java8中的 HashMap是由陣列+連結串列+紅黑樹組成的,當連結串列的長度超過8個時,就會轉為紅黑樹,降低查詢時的時間複

ConcurrentHashMap原始碼解析

初始化 先看看ConcurrentHashMap中幾個重要的屬性: // 初始化容量大小 static final int DEFAULT_INITIAL_CAPACITY = 16; //預設負載因子 static final float DEFAULT_LOAD_FACTOR = 0.75f; //預

Java HashMap和ConcurrentHashMap原始碼解析

閱讀建議:四節基本上可以進行獨立閱讀,建議初學者可按照 Java7 HashMap -> Java7 ConcurrentHashMap -> Java8 HashMap -> Java8 ConcurrentHashMap 順序進行閱讀,可適

Java容器——HashMap(Java8原始碼解析(二)

在前文中介紹了HashMap中的重要元素,現在萬事俱備,需要刨根問底看看實現了。HashMap的增刪改查,都離不開元素查詢。查詢分兩部分,一是確定元素在table中的下標,二是確定以指定下標元素為首的具體位置。可以抽象理解為二維陣列,第一個通過雜湊函式得來,第二個下標則是連結串列或紅黑樹來得到,下面

Java容器——HashMap(Java8原始碼解析(一)

一 概述 HashMap是最常用的Java資料結構之一,是一個具有常數級別的存取速率的高效容器。相對於List,Set等,結構相對複雜,本篇我們先對HashMap的做一個基本說明,對組成元素和構造方法進行介紹。 二 繼承關係 首先看HashMap的繼承關係,比較簡單,實現了Map和序列化

ConcurrentHashMap原始碼解析(3)

此文已由作者趙計剛授權網易雲社群釋出。 歡迎訪問網易雲社群,瞭解更多網易技術產品運營經驗。 4、get(Object key) 使用方法: map.get("hello"); 原始碼:  ConcurrentHashMap的get(Object key)  

第二章 ConcurrentHashMap原始碼解析

注:在看這篇文章之前,如果對HashMap的層不清楚的話,建議先去看看HashMap原始碼解析。 1、對於ConcurrentHashMap需要掌握以下幾點 Map的建立:ConcurrentHashMap() 往Map中新增鍵值對:即put(Object key, Object value)方

Java8 ThreadLocal 原始碼解析

前言 ThreadLocal ,像是一個神祕的黑衣人,令人望而生畏。唯有下定決心,一探究竟,方能解開他神祕的面紗、在Android中,Handler,EventBus,ConnectionPool 等等,都曾出現它的身影 是什麼東西? 看到Thread

Java併發程式設計與技術內幕:ConcurrentHashMap原始碼解析

       摘要:本文主要講了Java中ConcurrentHashMap 的原始碼       ConcurrentHashMap 是java併發包中非常有用的一個類,在高併發場景用得非常多,它是執行緒安全的。要注意到雖然HashTable雖然也是執行緒安全的,但是它的效

ConcurrentHashMap原始碼解析 JDK8

一、簡介 上篇文章詳細介紹了HashMap的原始碼及原理,本文趁熱打鐵繼續分析ConcurrentHashMap的原理。 首先在看本文之前,希望對HashMap有一個詳細的瞭解。不然看直接看ConcurrentHashMap的原始碼還是有些費勁的。 相信對HashMap,HashTable有一定了解,應該知道

ConcurrentHashMap原始碼解析-Java7

目錄 一.ConcurrentHashMap的模型圖 二.原始碼分析-類定義   2.1 極簡ConcurrentHashMap定義   2.2 Segment內部類   2.3 HashEntry內部類   2.4 ConcurrentHashMap的重要常量 三.常用介面原始碼分析  

ConcurrentHashMap原始碼解析,多執行緒擴容

前面一篇已經介紹過了 HashMap 的原始碼: [HashMap原始碼解析、jdk7和8之後的區別、相關問題分析](https://www.cnblogs.com/lifegoeson/p/13628737.html) HashMap並不是執行緒安全的,他就一個普通的容器,沒有做相關的同步處理,因此執行

Java併發包原始碼學習系列:JDK1.8的ConcurrentHashMap原始碼解析

[toc] 系列傳送門: - [Java併發包原始碼學習系列:AbstractQueuedSynchronizer](https://blog.csdn.net/Sky_QiaoBa_Sum/article/details/112254373) - [Java併發包原始碼學習系列:CLH同步佇列及同步資源

ConcurrentHashMap實現原理以及原始碼解析

ConcurrentHashMap實現原理以及原始碼解析 ConcurrentHashMap是Java1.5中引用的一個執行緒安全的支援高併發的HashMap集合類。 1、執行緒不安全的HashMap 因為多執行緒環境下,使用Hashmap進行put操作會引起死迴圈

【搞定Java8新特性】之Java7/8 中的 HashMap 和 ConcurrentHashMap解析

本文轉載自:https://blog.csdn.net/a724888/article/details/68936953 本文目錄: 1、Java7 中的HashMap 1.1、put過程分析 陣列初始化 計算具體陣列位置 新增節點到連結串列中 陣列擴容 補充:Has

HashMap和ConcurrentHashMap 原始碼關鍵點解析

第一部分:關鍵原始碼講解 1.HashMap  是如何儲存的? a.底層是一個數組 tab b. hash=hash(key) ,然後根據陣列長度n和hash值,決定當前需要put的元素對應的陣列下標, hash演算法見紅框。    

HashMap原始碼解析ConcurrentHashMap、ConcurrentSkipListMap 初步熟悉 (JDK1.7之一)

    兜兜轉轉開發java有些時間,可是每次都是用到採取學習,用過又回到原點,反反覆覆,究其緣由還是沒有合理的整理總結,只是個不斷重複搬磚、運磚的中高階工種,沒有不可替代的核心競爭力。    why?因為你30min——1h能完成的東西,不熟悉的人多花點時間也是可以完成的,