jdk源碼解析三之JUC併發容器

本篇文章主要是對JUC包下,一些併發類的源碼分析,如果想了解具體實例,請點擊

併發容器

ConcurrentHashMap

在這裏插入圖片描述

 //使用了unSafe方法,通過直接操作內存的方式來保證併發處理的安全性,使用的是硬件的安全機制。
    /*
     * 用來返回節點數組的指定位置的節點的原子操作
     */
    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);
    }

    /*
     * cas原子操作,在指定位置設定值
     */
    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

    public V put(K key, V value) {

        /*
         * onlyIfAbsent
         *  false:這個value一定會設置
         *  true:只有當這個key的value爲空的時候纔會設置
         */
        return putVal(key, value, false);
    }

    final V putVal(K key, V value, boolean onlyIfAbsent) {
        //不允許key/value爲null,否則及時失敗
        if (key == null || value == null) throw new NullPointerException();
        //獲取key的hashCode
        int hash = spread(key.hashCode());
        //用來計算在這個節點總共有多少個元素,用來控制擴容或者轉移爲樹
        int binCount = 0;
        for (Node<K,V>[] tab = table;;) {
            Node<K,V> f; int n, i, fh;
            //初始化tab
            if (tab == null || (n = tab.length) == 0)
                tab = initTable();
            else if (
                //通過哈希計算出一個表中的位置因爲n是數組的長度,所以(n-1)&hash肯定不會出現數組越界
                    (f = tabAt(tab, i = (n - 1) & hash)) == null) {
                //如果這個位置沒有元素的話,則通過cas的方式嘗試添加,注意這個時候是沒有加鎖的
                if (casTabAt(tab, i, null,
                             new Node<K,V>(hash, key, value, null)))
                    break;                   // no lock when adding to empty bin
            }
            /*
             * 如果檢測到某個節點的hash值是MOVED,則表示正在進行數組擴張的數據複製階段,
             * 則當前線程也會參與去複製,通過允許多線程複製的功能,一次來減少數組的複製所帶來的性能損失
             */
            else if ((fh = f.hash) == MOVED)
                tab = helpTransfer(tab, f);
            else {
                V oldVal = null;
                synchronized (f) {
                    //再次取出要存儲的位置的元素,跟前面取出來的比較
                    if (tabAt(tab, i) == f) {
                        //取出來的元素的hash值大於0,當轉換爲樹之後,hash值爲-2
                        if (fh >= 0) {
                            binCount = 1;
                            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)
                                        e.val = value;
                                    break;
                                }
                                Node<K,V> pred = e;
                                //找到最後,沒找到值的,則新建對象.
                                if ((e = e.next) == null) {
                                    //添加鏈表尾端
                                    pred.next = new Node<K,V>(hash, key,
                                                              value, null);
                                    break;
                                }
                            }
                        }
                        //對紅黑樹的處理
                        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;
                }
            }
        }
        //計數
        addCount(1L, binCount);
        return null;
    }

初始化

   private final Node<K,V>[] initTable() {
        Node<K,V>[] tab; int sc;
        while ((tab = table) == null || tab.length == 0) {
            //sizeCtl初始值爲0,當小於0的時候表示在別的線程在初始化表或擴展表
            //則暫停當前正在執行的線程對象,並執行其他線程。
            if ((sc = sizeCtl) < 0)
                Thread.yield(); // lost initialization race; just spin
            else if (
                  /*
                   SIZECTL:當前內存偏移量,
                    sc:期望值
                    -1:表示要替換的值
                */
                    U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
                try {
                    if ((tab = table) == null || tab.length == 0) {
                        //指定了大小的時候就創建指定大小的Node數組,否則創建指定大小(16)的Node數組
                        int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
                        @SuppressWarnings("unchecked")
                        Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
                        table = tab = nt;
                        sc = n - (n >>> 2);
                    }
                } finally {
                    //初始化後,sizeCtl長度爲數組長度的3/4
                    sizeCtl = sc;
                }
                break;
            }
        }
        return tab;
    }

擴容

    /**
     * Replaces all linked nodes in bin at given index unless table is
     * too small, in which case resizes instead.
     * 數組長度<64,則擴容一倍
     * 否則轉成樹
     */
    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);
                            //把Node組成的鏈表,轉化爲TreeNode的鏈表,頭結點依然放在相同的位置
                            if ((p.prev = tl) == null)
                                hd = p;
                            else
                                tl.next = p;
                            tl = p;
                        }
                        //把TreeNode的鏈表放入容器TreeBin中,內部將單節點樹轉換成紅黑樹
                        setTabAt(tab, index, new TreeBin<K,V>(hd));
                    }
                }
            }
        }
    }

 private final void tryPresize(int size) {
        //擴容大小>=最大的一半,直接設置成最大容量
        int c = (size >= (MAXIMUM_CAPACITY >>> 1)) ? MAXIMUM_CAPACITY :
                //返回大於輸入參數且最近的2的整數次冪的數
            tableSizeFor(size + (size >>> 1) + 1);
        int sc;
        while ((sc = sizeCtl) >= 0) {
            Node<K,V>[] tab = table; int n;
            //如果數組還沒有初始化
            //putAll的時候,會執行這兒
            if (tab == null || (n = tab.length) == 0) {
                n = (sc > c) ? sc : c;
                //SIZECTL設置-1,表示正在初始化
                if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
                    try {
                        //雙重檢查
                        if (table == tab) {
                            @SuppressWarnings("unchecked")
                            Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
                            table = nt;
                            //sc=3/4*n
                            sc = n - (n >>> 2);
                        }
                    } finally {
                        sizeCtl = sc;
                    }
                }
            }
            //擴容後的大小<=sizeCtl或者當前數組長度>容量上限,則退出
            else if (c <= sc || n >= MAXIMUM_CAPACITY)
                break;
            else if (tab == table) {
                int rs = resizeStamp(n);
                //表示正在擴容
                if (sc < 0) {
                    Node<K,V>[] nt;
                    if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
                        sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||
                        transferIndex <= 0)
                        break;
                    //transfer線程數+1,當前線程將加入對transfer的處理
                    //transfer的時候,sc表示在transfer工作的線程數
                    if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))
                        transfer(tab, nt);
                }
                //沒有在初始化或擴容,則開始擴容
                else if (U.compareAndSwapInt(this, SIZECTL, sc,
                                             (rs << RESIZE_STAMP_SHIFT) + 2))
                    transfer(tab, null);
            }
        }
    }


    private static final int tableSizeFor(int c) {

        /*
          讓cap-1再賦值給n的目的是另找到的目標值大於或等於原值。例如二進制1000,十進制數值爲8。
        如果不對它減1而直接操作,將得到答案10000,即16。顯然不是結果。
        減1後二進制爲111,再進行操作則會得到原來的數值1000,即8。
         */
        int n = c - 1;
        n |= n >>> 1;
        n |= n >>> 2;
        n |= n >>> 4;
        n |= n >>> 8;
        n |= n >>> 16;
        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
    }

 private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {
        int n = tab.length, stride;
        //MIN_TRANSFER_STRIDE=16.控制線程數
        //每個CPU最少處理16個長度的數組元素,也就是說,如果一個數組的長度只有16,那只有一個線程會對其進行擴容的複製移動操作
        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;
        /*
         * 創建一個fwd節點,這個是用來控制併發的,當一個節點爲空或已經被轉移之後,就設置爲fwd節點
         * 這是一個空的標誌節點
         */
        ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab);
        //是否繼續向前查找的標誌位
        boolean advance = true;
        //在完成之前重新在掃描一遍數組,看看有沒完成的沒
        boolean finishing = false; // to ensure sweep before committing nextTab
        for (int i = 0, bound = 0;;) {
            Node<K,V> f; int fh;
            while (advance) {
                int nextIndex, nextBound;
                if (--i >= bound || finishing)
                    advance = false;
                else if ((nextIndex = transferIndex) <= 0) {
                    i = -1;
                    advance = false;
                }
                else if (U.compareAndSwapInt
                         (this, TRANSFERINDEX, nextIndex,
                          nextBound = (nextIndex > stride ?
                                       nextIndex - stride : 0))) {
                    //如果一個數組的長度只有16,只有一個線程會對其進行擴容的複製移動操作
                    bound = nextBound;
                    i = nextIndex - 1;
                    advance = false;
                }
            }
            if (i < 0 || i >= n || i + n >= nextn) {
                int sc;
                //已經完成轉移
                if (finishing) {
                    nextTable = null;
                     //這裏完成nextTab=>table轉換
                    table = nextTab;
                    //爲擴容後的0.75
                    sizeCtl = (n << 1) - (n >>> 1);
                    return;
                }
                //正在工作的線程數-1,並返回
                if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) {
                    if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT)
                        return;
                    finishing = advance = true;
                    i = n; // recheck before commit
                }
            }
            else if ((f = tabAt(tab, i)) == null)
                //數組中把null的元素設置爲ForwardingNode節點(hash值爲MOVED)
                advance = casTabAt(tab, i, null, fwd);
            else if ((fh = f.hash) == MOVED)
                //表示已有線程正在處理
                advance = true; // already processed
            else {
                synchronized (f) {
                    //雙重檢查加鎖
                    if (tabAt(tab, i) == f) {
                        Node<K,V> ln, hn;
                        //>=0說明是node節點
                        if (fh >= 0) {
                            //爲0則表示放在擴容後數組當前索引下,否則放在n+之前位置索引下
                            int runBit = fh & n;
                            Node<K,V> lastRun = f;
                            /*
                            循環結束之後,runBit就是最後不變的hash&n的值
                            也就是說由lastRun節點後的hash&n的值一樣,這樣就可以直接保存,而不需要處理後面的節點
                             */
                            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;
                            }
                            //分別逆序存入ln或hn鏈表中
                            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);
                            //設置fwd,這樣其他線程執行的時候,會跳過去.
                            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;
                                }
                            }
                            /*
                             * 在複製完樹節點之後,判斷該節點處構成的樹還有幾個節點,
                             * 如果≤6個的話,就轉爲一個鏈表
                             */
                            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;
                        }
                    }
                }
            }
        }
    }


    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) {
                if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
                    sc == rs + MAX_RESIZERS || transferIndex <= 0)
                    break;
                //線程數+1,幫助一起轉換
                if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) {
                    transfer(tab, nextTab);
                    break;
                }
            }
            return nextTab;
        }
        return table;
    }

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) {
            //如果first匹配key-value,則直接返回
            if ((eh = e.hash) == h) {
                if ((ek = e.key) == key || (ek != null && key.equals(ek)))
                    return e.val;
            }
            else if (eh < 0)
                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;
    }

replace#remove

    public V remove(Object key) {
        return replaceNode(key, null, null);
    }
 
    /**
     * {@inheritDoc}
     *
     * @throws NullPointerException if any of the arguments are null
     * key映射有值,值爲oldValue,則更新
     */
    public boolean replace(K key, V oldValue, V newValue) {
        if (key == null || oldValue == null || newValue == null)
            throw new NullPointerException();
        return replaceNode(key, newValue, oldValue) != null;
    }


    final V replaceNode(Object key, V value, Object cv) {
        int hash = spread(key.hashCode());
        for (Node<K,V>[] tab = table;;) {
            Node<K,V> f; int n, i, fh;
            //空判斷
            if (tab == null || (n = tab.length) == 0 ||
                (f = tabAt(tab, i = (n - 1) & hash)) == null)
                break;
            //正在擴容,則加入擴容隊伍
            else if ((fh = f.hash) == MOVED)
                tab = helpTransfer(tab, f);
            else {
                V oldVal = null;
                boolean validated = false;
                synchronized (f) {
                    //雙重檢查加鎖,DCL
                    if (tabAt(tab, i) == f) {
                        //對鏈表的處理
                        if (fh >= 0) {
                            validated = true;
                            for (Node<K,V> e = f, pred = null;;) {
                                K ek;
                                //匹配key
                                if (e.hash == hash &&
                                    ((ek = e.key) == key ||
                                     (ek != null && key.equals(ek)))) {
                                    V ev = e.val;
                                    //匹配value
                                    if (cv == null || cv == ev ||
                                        (ev != null && cv.equals(ev))) {
                                        oldVal = ev;
                                        //修改
                                        if (value != null)
                                            e.val = value;
                                        //value=null,說明執行刪除操作
                                        else if (pred != null)
                                            pred.next = e.next;
                                        //當匹配的值是first節點的處理
                                        else
                                            setTabAt(tab, i, e.next);
                                    }
                                    break;
                                }
                                //臨時保存上一個節點,便於執行刪除節點操作
                                pred = e;
                                //執行最後一個節點,則退出
                                if ((e = e.next) == null)
                                    break;
                            }
                        }
                        //紅黑樹的處理
                        else if (f instanceof TreeBin) {
                            validated = true;
                            TreeBin<K,V> t = (TreeBin<K,V>)f;
                            TreeNode<K,V> r, p;
                            if ((r = t.root) != null &&
                                (p = r.findTreeNode(hash, key, null)) != null) {
                                V pv = p.val;
                                if (cv == null || cv == pv ||
                                    (pv != null && cv.equals(pv))) {
                                    oldVal = pv;
                                    if (value != null)
                                        p.val = value;
                                    else if (t.removeTreeNode(p))
                                        setTabAt(tab, i, untreeify(t.first));
                                }
                            }
                        }
                    }
                }
                if (validated) {
                    if (oldVal != null) {
                        if (value == null)
                            //記數-1
                            addCount(-1L, -1);
                        return oldVal;
                    }
                    break;
                }
            }
        }
        return null;
    }

總結:

什麼時候擴容?

  1. 單節點容量>=8且容量<64,則擴容一倍
  2. 當數組中元素達到了sizeCtl的數量的時候,則會調用transfer方法來進行擴容

沒有實現對map進行加鎖來執行獨佔訪問,因爲採用了分段鎖,所以無法使用客戶端加鎖來創建新的原子操作,如若沒有則添加之內操作.

JDK1.8放棄分段鎖
段Segment繼承了重入鎖ReentrantLock,有了鎖的功能,每個鎖控制的是一段,當每個Segment越來越大時,鎖的粒度就變得有些大了。
分段鎖的優勢在於保證在操作不同段 map 的時候可以併發執行,操作同段 map 的時候,進行鎖的競爭和等待。這相對於直接對整個map同步synchronized是有優勢的。
缺點在於分成很多段時會比較浪費內存空間(不連續,碎片化); 操作map時競爭同一個分段鎖的概率非常小時,分段鎖反而會造成更新等操作的長時間等待; 當某個段很大時,分段鎖的性能會下降。

jdk1.8的map實現
和hashmap一樣,jdk 1.8中ConcurrentHashmap採用的底層數據結構爲數組+鏈表+紅黑樹的形式。數組可以擴容,鏈表可以轉化爲紅黑樹。

爲什麼不用ReentrantLock而用synchronized ?
減少內存開銷:如果使用ReentrantLock則需要節點繼承AQS來獲得同步支持,增加內存開銷,而1.8中只有頭節點需要進行同步。
內部優化:synchronized則是JVM直接支持的,JVM能夠在運行時作出相應的優化措施:鎖粗化、鎖消除、鎖自旋等等。

多個線程又是如何同步處理的呢?

  1. 同步處理主要是通過Synchronized和unsafe兩種方式來完成的。
  2. 在取得sizeCtl、某個位置的Node的時候,使用的都是unsafe的方法,來達到併發安全的目的
    當需要在某個位置設置節點的時候,則會通過Synchronized的同步機制來鎖定該位置的節點。
  3. 在數組擴容的時候,則通過處理的步長和fwd節點來達到併發安全的目的,通過設置hash值爲MOVED
  4. 當把某個位置的節點複製到擴張後的table的時候,也通過Synchronized的同步機制來保證現程安全

CopyOnWriteArrayList

寫入時複製,只要正確發佈一個事實不可變對象,在訪問該對象時就不再需要進一步同步,在每次修改時,都會創建並重新發佈一個新的容器副本,從而實現可變性.
使用場景:迭代>修改,事件通知系統(註冊和註銷事件監聽器操作少於接收事件通知的操作)

   //構造時,初始化容量爲0的Object數組
    public CopyOnWriteArrayList() {
        setArray(new Object[0]);
    }

add

    public boolean add(E e) {
        final ReentrantLock lock = this.lock;
        //加鎖
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            //創建數組副本,容量+1
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            newElements[len] = e;
            //更新舊的數組
            setArray(newElements);
            return true;
        } finally {
            //解鎖
            lock.unlock();
        }
    }

remove

    public E remove(int index) {
        final ReentrantLock lock = this.lock;
        //加鎖
        lock.lock();
        try {
            //獲取數組以及當前值
            Object[] elements = getArray();
            int len = elements.length;
            E oldValue = get(elements, index);
            
            int numMoved = len - index - 1;
            //最後一個數組索引,則直接copy
            if (numMoved == 0)
                setArray(Arrays.copyOf(elements, len - 1));
            else {
                //copy2次
                Object[] newElements = new Object[len - 1];
                System.arraycopy(elements, 0, newElements, 0, index);
                System.arraycopy(elements, index + 1, newElements, index,
                                 numMoved);
                setArray(newElements);
            }
            return oldValue;
        } finally {
            //解鎖
            lock.unlock();
        }
    }

get

   public E get(int index) {
        return get(getArray(), index);
    }
    private E get(Object[] a, int index) {
        return (E) a[index];
    }

set

public E set(int index, E element) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            E oldValue = get(elements, index);

            if (oldValue != element) {
                int len = elements.length;
                //副本修改
                Object[] newElements = Arrays.copyOf(elements, len);
                newElements[index] = element;
                //副本設置爲當前數組
                setArray(newElements);
            } else {
                // Not quite a no-op; ensures volatile write semantics
                 //保證最終一致性
                setArray(elements);
            }
            return oldValue;
        } finally {
            lock.unlock();
        }
    }

總結

應用場景:讀多寫少
如:黑名單,監聽器

讀上沒加鎖,所以支持大量併發,但涉及修改的時候,有2個數組副本,當數組過大,則造成多餘的內存消耗.

CopyOnWrite容器只能保證數據的最終一致性,不能保證數據的實時一致性。之所以只能保證最終一致,是因爲每次涉及到修改都會copy一個副本然後回寫,最終的結果是一致的,但是在copy途中如果有讀操作,那麼就會造成數據不一致問題.

LinkedBlockingQueue

一個基於鏈表的阻塞隊列。此隊列按 FIFO(先進先出)排序元素

   public LinkedBlockingQueue() {
        //默認最大容量
        this(Integer.MAX_VALUE);
    }

    public LinkedBlockingQueue(int capacity) {
        if (capacity <= 0) throw new IllegalArgumentException();
        this.capacity = capacity;
        //維護雙端隊列
        last = head = new Node<E>(null);
    }

put

//put將指定元素插入此隊列尾部,將等待可用的空間
    public void put(E e) throws InterruptedException {
        if (e == null) throw new NullPointerException();
        // Note: convention in all put/take/etc is to preset local var
        // holding count negative to indicate failure unless set.
        int c = -1;
        //創建新節點
        Node<E> node = new Node<E>(e);
        //獲取put鎖
        final ReentrantLock putLock = this.putLock;
        final AtomicInteger count = this.count;
        //如果當前線程未被中斷,則獲取鎖。
        putLock.lockInterruptibly();
        try {
            //達到上限容量,則一直等待
            while (count.get() == capacity) {
                notFull.await();
            }
            //設置值
            enqueue(node);
            c = count.getAndIncrement();
            if (c + 1 < capacity)
                //列是否有可用空間,如果有則喚醒一個等待線程
                notFull.signal();
        } finally {
            //釋放鎖
            putLock.unlock();
        }
        // 如果隊列中有一條數據,喚醒消費線程進行消費
        if (c == 0)
            signalNotEmpty();
    }

offer

    public boolean offer(E e) {
        if (e == null) throw new NullPointerException();
        //等於最大容量,則返回,而不阻塞
        final AtomicInteger count = this.count;
        if (count.get() == capacity)
            return false;

        int c = -1;
        Node<E> node = new Node<E>(e);
        final ReentrantLock putLock = this.putLock;
        //因爲不阻塞,所以直接獲取鎖
        putLock.lock();
        try {
            //再次檢查容量大小,然後直接添加,隨後喚醒一個等待線程
            if (count.get() < capacity) {
                enqueue(node);
                c = count.getAndIncrement();
                if (c + 1 < capacity)
                    notFull.signal();
            }
        } finally {
            putLock.unlock();
        }
        // 如果隊列中有一條數據,喚醒消費線程進行消費
        if (c == 0)
            signalNotEmpty();
        return c >= 0;
    }

阻塞時間的offer

 public boolean offer(E e, long timeout, TimeUnit unit)
        throws InterruptedException {

        if (e == null) throw new NullPointerException();
        long nanos = unit.toNanos(timeout);
        int c = -1;
        final ReentrantLock putLock = this.putLock;
        final AtomicInteger count = this.count;
        //獲取中斷鎖
        putLock.lockInterruptibly();
        try {
            //等於最大容量,則一直循環
            while (count.get() == capacity) {
                //超過超時時間則返回
                if (nanos <= 0)
                    return false;
                //當前線程在接到信號、被中斷或到達指定等待時間之前一直處於等待狀態。
                nanos = notFull.awaitNanos(nanos);
            }
            enqueue(new Node<E>(e));
            c = count.getAndIncrement();
            //通知信號
            if (c + 1 < capacity)
                notFull.signal();
        } finally {
            putLock.unlock();
        }
        // 如果隊列中有一條數據,喚醒消費線程進行消費
        if (c == 0)
            signalNotEmpty();
        return true;
    }

take

//獲取並移除此隊列的頭部,在元素變得可用之前一直等待
 public E take() throws InterruptedException {
        E x;
        int c = -1;
        final AtomicInteger count = this.count;
        final ReentrantLock takeLock = this.takeLock;
        //中斷點
        takeLock.lockInterruptibly();
        try {
            //隊列爲空,阻塞等待
            while (count.get() == 0) {
                notEmpty.await();
            }
            //獲取值
            x = dequeue();
            c = count.getAndDecrement();
            // 隊列中還有元素,喚醒下一個消費線程進行消費
            if (c > 1)
                notEmpty.signal();
        } finally {
            takeLock.unlock();
        }
        // 之前隊列是滿的,則喚醒生產線程進行添加元素
        if (c == capacity)
            signalNotFull();
        return x;
    }

 private E dequeue() {
        // assert takeLock.isHeldByCurrentThread();
        // assert head.item == null;
         //head默認root的value是null
        Node<E> h = head;
        Node<E> first = h.next;
        // head節點原來指向的節點的next指向自己,等待下次gc回收
        h.next = h; // help GC
        // head節點指向下一個節點
        head = first;
        //獲取新的head的value
        E x = first.item;
        //新的head設置null
        first.item = null;
        return x;
    }

poll

    public E poll() {
        final AtomicInteger count = this.count;
        //容量爲0,直接返回
        if (count.get() == 0)
            return null;
        E x = null;
        int c = -1;
        final ReentrantLock takeLock = this.takeLock;
        takeLock.lock();
        try {
            if (count.get() > 0) {
                x = dequeue();
                c = count.getAndDecrement();
                if (c > 1)
                    notEmpty.signal();
            }
        } finally {
            takeLock.unlock();
        }
        if (c == capacity)
            signalNotFull();
        return x;
    }

peek

    public E peek() {
        if (count.get() == 0)
            return null;
        final ReentrantLock takeLock = this.takeLock;
        takeLock.lock();
        try {
            Node<E> first = head.next;
            if (first == null)
                return null;
            else
                return first.item;
        } finally {
            takeLock.unlock();
        }
    }

remove

    public boolean remove(Object o) {
        //爲null,直接返回
        if (o == null) return false;
        //put和take鎖,就暫時不能新增或修改
        fullyLock();
        try {
            for (Node<E> trail = head, p = trail.next;
                 p != null;
                 trail = p, p = p.next) {
                //匹配到值,則刪除
                if (o.equals(p.item)) {
                    unlink(p, trail);
                    return true;
                }
            }
            return false;
        } finally {
            //釋放2個鎖
            fullyUnlock();
        }
    }

    void unlink(Node<E> p, Node<E> trail) {
        // assert isFullyLocked();
        // p.next is not changed, to allow iterators that are
        // traversing p to maintain their weak-consistency guarantee.
        p.item = null;
          //在迭代的時候,如果p.next爲null,則會造成異常.所以這裏沒設置null
        trail.next = p.next;
        if (last == p)
            last = trail;
        // 如果刪除之前元素是滿的,刪除之後就有空間了,喚醒生產線程放入元素
        if (count.getAndDecrement() == capacity)
            notFull.signal();
    }

迭代器

當執行迭代器的nextNode的時候,如果同時發現有執行take操作,因爲當前head.next指向了自己,
在這裏插入圖片描述

        private Node<E> nextNode(Node<E> p) {
            for (;;) {
                Node<E> s = p.next;
                //take時,head.next=head,則直接返回當前head的下一個節點
                if (s == p)
                    return head.next;
                if (s == null || s.item != null)
                    return s;
                p = s;
            }
        }

總結

底層阻塞隊列FIFO.內部由兩個ReentrantLock來實現出入隊列的線程安全,由各自的Condition對象的await和signal來實現等待和喚醒功能。
默認容量無界,且底層鏈表,所以執行插入和刪除效率比較高.且2把鎖維護新增刪除,所以併發有所提高.

ArrayBlockingQueue

    public ArrayBlockingQueue(int capacity, boolean fair) {
        if (capacity <= 0)
            throw new IllegalArgumentException();
        this.items = new Object[capacity];
        //一把鎖管理
        lock = new ReentrantLock(fair);
        notEmpty = lock.newCondition();
        notFull =  lock.newCondition();
    }

put

    public void put(E e) throws InterruptedException {
        //value不允許爲null
        checkNotNull(e);
        final ReentrantLock lock = this.lock;
        //中斷鎖
        lock.lockInterruptibly();
        try {
            //當前count超過生產的容量上限,則等待
            while (count == items.length)
                notFull.await();
            enqueue(e);
        } finally {
            //釋放鎖
            lock.unlock();
        }
    }

private void enqueue(E x) {
        // assert lock.getHoldCount() == 1;
        // assert items[putIndex] == null;
        final Object[] items = this.items;
        items[putIndex] = x;
        //當前個數超過下標,則回滾爲0下標
        if (++putIndex == items.length)
            putIndex = 0;
        //當前可進行消息個數+1
        count++;
        //喚醒消費線程進行消費
        notEmpty.signal();
    }
    

offer

    public boolean offer(E e) {
        checkNotNull(e);
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            //當前消息個數超過上限,則直接返回
            if (count == items.length)
                return false;
            else {
                enqueue(e);
                return true;
            }
        } finally {
            lock.unlock();
        }
    }

take

    public E take() throws InterruptedException {
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            //當前沒有消息,則等待
            while (count == 0)
                notEmpty.await();
            //消費消息
            return dequeue();
        } finally {
            lock.unlock();
        }
    }

    private E dequeue() {
        // assert lock.getHoldCount() == 1;
        // assert items[takeIndex] != null;
        final Object[] items = this.items;
        @SuppressWarnings("unchecked")
                //獲取消息
        E x = (E) items[takeIndex];
        //清空消息
        items[takeIndex] = null;
        //當前消費的索引爲數組長度,則從0開始消費消息
        if (++takeIndex == items.length)
            takeIndex = 0;
        count--;
        //迭代器衝突處理
        if (itrs != null)
            itrs.elementDequeued();
        //喚醒生產線程,生產消息
        notFull.signal();
        return x;
    }

        void elementDequeued() {
            // assert lock.getHoldCount() == 1;
            //消費個數爲0,則清空迭代器值
            if (count == 0)
                queueIsEmpty();
            else if (takeIndex == 0)
                //消費的索引回滾爲0處理
                takeIndexWrapped();
        }

remove

public boolean remove(Object o) {
        if (o == null) return false;
        final Object[] items = this.items;
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            if (count > 0) {
                final int putIndex = this.putIndex;
                int i = takeIndex;
                //遍歷takeIndex ~ putIndex之間的數據,如果涉及到邊界問題,則從0開始查找
                do {
                    //匹配則刪除
                    if (o.equals(items[i])) {
                        removeAt(i);
                        return true;
                    }
                    //邊界檢測.
                    if (++i == items.length)
                        i = 0;
                } while (i != putIndex);
            }
            return false;
        } finally {
            lock.unlock();
        }
    }

 void removeAt(final int removeIndex) {
        // assert lock.getHoldCount() == 1;
        // assert items[removeIndex] != null;
        // assert removeIndex >= 0 && removeIndex < items.length;
        final Object[] items = this.items;
        //如果刪除的消息和目前正take索引相同
        if (removeIndex == takeIndex) {
            // removing front item; just advance
            //清空當前take消息
            items[takeIndex] = null;
            //takeIndex偏移到下一個
            if (++takeIndex == items.length)
                takeIndex = 0;
            count--;
            //迭代器衝突處理
            if (itrs != null)
                itrs.elementDequeued();
        } else {
            // an "interior" remove

            // slide over all others up through putIndex.
            final int putIndex = this.putIndex;
            for (int i = removeIndex;;) {
                int next = i + 1;
                //達到數組上線,索引設爲0,開始遍歷
                if (next == items.length)
                    next = 0;
                //未到邊界,則偏移到下一個數組值
                if (next != putIndex) {
                    items[i] = items[next];
                    i = next;
                } else {
                    //到了邊界,設置當前items[putIndex]值爲null,且更新putIndex
                    items[i] = null;
                    this.putIndex = i;
                    break;
                }
            }
            //當前消息個數-1
            count--;
            //迭代器衝突處理
            if (itrs != null)
                itrs.removedAt(removeIndex);
        }
        notFull.signal();
    }

總結

有界阻塞隊列。此隊列按 FIFO(先進先出)原則對元素進行排序。
初始化必須設置容量,value不允許爲空
全局一個鎖處理,相對併發比較低.
因爲底層數組,所以修改查詢快.

PriorityBlockingQueue

SynchronousQueue

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章