java高併發之ConcurrentSkipListMap的那些事

whale_tail發表於2022-03-16

注意:本文內容基於JDK11,不同版本會有差異

ConcurrentSkipListMap的結構

ConcurrentSkipListMap是以連結串列(自然排序)的形式進行資料儲存的。即在類中通過定義Node內部類來儲存單個節點的資料,通過Node中的next屬性,來記錄連結串列的下一個節點。同時會對Node中記錄的資料建立多級索引。結構總體如圖所示:
在這裡插入圖片描述

原始碼解析

本文以put方法來對ConcurrentSkipListMap進行解析。
ConcurrentSkipListMap會在put方法裡呼叫doPut方法。所以doPut()才是最終的實現
在這裡插入圖片描述
以下動圖為doPut方法的動態演示:
插入元素的動態演示
對於doPut方法的理解,可以通過呼叫ConcurrentSkipListMap的put方法。斷點除錯,配合說明進行理解加深
關鍵程式碼說明:

   /**
     *
     * @param key
     * @param value
     * @param onlyIfAbsent 如果已經存在,是否不進行插入(false就是進行插入,true就是不進行插入)
     * @return
     */
    private V doPut(K key, V value, boolean onlyIfAbsent){
        if(key == null){
            throw new NullPointerException();
        }
        //比較器
        Comparator<? super K> cmp = comparator;
        for(;;){
            //頭索引
            Index<K,V> h;
            Node<K,V> b;
            //禁止重排序
            VarHandle.acquireFence();
            //級別
            int levels = 0;
	   /**
	    * 在第一次進行put方法時,會對head進行一個初始化操作。head是ConcurrentSkipListMap類的入口。
	    * 因為是連結串列結構,所以所有的操作都需要從head開始
	    * 這裡定義了一個null的Node節點,並且把這個Node賦值給Index(Index可以通過檢視原始碼的內部類),即當前索引所對應的節點就是該Node節點
	    * 這裡定義的Node不儲存資料,僅僅是作為整個連結串列的開始,可以配合結構圖進行理解
	    * compareAndSet 原子操作,保證高併發下的原子性。
	    */
            if( (h = head) == null){
                //第一次初始化操作時會進入到這個if裡
                Node<K,V> base = new Node<K,V>(null, null, null);
                h = new Index<K,V>(base, null, null);
                b = HEAD.compareAndSet(this, null, h) ? base : null;
            }
            else{
                /**
		 * 這裡包含了兩個迴圈
		 * while迴圈是對索引的橫向查詢,一直找到right為空或者需要插入的key小於已存在的key的索引的位置
		 * for迴圈則是進行縱向查詢,即查詢到多層索引中的最底層索引
		 * cpr()方法是對兩個key的自然排序比較。本質上使用的是compareTo方法進行比較
		 */
                for (Index<K,V> q = h, r, d;;){
                    //通過while迴圈查詢合適的索引位置橫行查詢
                    while((r = q.right) != null){
                        Node<K,V> p;
                        K k;
                        if((p = r.node) == null || (k = p.key) == null || p.val == null){
                            RIGHT.compareAndSet();
                        }
                        else if(cpr(cmp, key, k) > 0){
                            q = r;
                        }
                        else{
                            break;
                        }
                    }
                    if(( d = q.down) != null){
                        ++levels;
                        q = d;
                    }
                    else{
                        b = q.node;
                        break;
                    }
                }
            }
            if(b != null){
                Node<K,V> z = null;
                /**
                 * 這裡通過for迴圈來查詢插入點,即key的值需要大於插入點之前Node的key的值且小於插入點之後Node的key的值
                 */
                for (;;){
                    Node<K,V> n, p;
                    K k;
                    V v;
                    int c;
                    if( (n = b.next) == null){
                        if(b.key == null){
                            cpr(cmp, key, key);
                        }
                        c = -1;
                    }
                    else if((k = n.key) == null){
                        break;
                    }
                    else if((v = n.val) == null){
                        c = 1;
                    }
                    else if((c = cpr(cmp, key, k)) > 0){
                        //如果key > k
                        //那麼將n對應的node賦值給b。也就是重置b,將下一個Node的物件賦值到當前的b上
                        //同時將1賦值給c,然後進入下一次迴圈
                        b = n;
                    }
                    else {
                        c = 1;
                    }
					
					//具體的插入操作就是在這實現的
                    if(c < 0 && NEXT.compareAndSet(b, n, p = new Node<K,V>(key, value, n))){
                        z = p;
                        //跳出本次迴圈
                        break;
                    }
                }

                if(z != null){
                    //原始碼中使用ThreadLocalRandom.nextSecondarySeed()方法。
                    // 但是我們無法使用,所以用這個臨時替代。保證不報錯
                    int lr = ThreadLocalRandom.current().nextInt();
                    //1/4的概率新增索引
                    if((lr & 0x3) == 0 ){
                        int hr = ThreadLocalRandom.current().nextInt();
                        long rnd = hr << 32 | lr & 0xffffffffL;
                        //新增之前級別需要下降
                        int skips = levels;
                        Index<K,V> x = null;
                        //for迴圈表示,當前節點如果需要生成索引,那麼需要根據索引的層級來判斷生產多少層的索引
                        for(;;){
                            x = new Index<K,V>(z, x,null);
                            if (rnd >= 0L || --skips < 0){
                                break;
                            }
                            else{
                                rnd <<= 1;
                            }
                        }
                        //addIndices是具體索引生成的方法
                        //該方法返回boolean型別的資料,如果索引生成成功,那麼返回true,如果索引插入失敗,那麼返回false。
                        //這個if判斷是代表如果當前索引生成成功,那麼在當前索引的基礎上再生成上一級索引(對索引再生成一層索引)。
                        if(addIndices(h, skips, x, cmp) && skips < 0 && head == h){
							Index<K,V> hx = new Index<K,V>(z, x, null);
							//生成頭索引
                            Index<K,V> nh = new Index<K,V>(h.node, h, hx);
                            HEAD.compareAndSet(this, h, nh);
                        }
                        if (z.val == null){

                        }
                    }
                    //元素技術進行+1操作
                    addCount(1L);
                    return null;
                }
            }
        }
    }

相關文章