Exchanger 1.8源碼分析

java.util.concurrent*包中的Exchanger類可用於兩個線程之間交換信息

可簡單地將Exchanger對象理解爲一個包含兩個格子的容器,通過exchanger方法可以向兩個格子中填充信息。當兩個格子中的均被填充時,該對象會自動將兩個格子的信息交換,然後返回給線程,從而實現兩個線程的信息交換。

源碼:
構造函數

public Exchanger() {
		//Participant 是一個ThreadLocal子類,初始化一個Node節點
        participant = new Participant();
    }

Node節點:存儲交換數據的節點。

  1. 每個線程都有一個自己的Node,存儲在Participant即ThreadLocal對象當中
  2. Exchanger對象是多個線程共享的,要交換的數據存儲在Exchanger.slot的Node成員對象中
/**
     * Nodes hold partially exchanged data, plus other per-thread
     * bookkeeping. Padded via @sun.misc.Contended to reduce memory
     * contention.
     */
    @sun.misc.Contended static final class Node {
        int index;              // Arena 序號,Arena 是一個CAS失敗後進行等待的Node[]
        int bound;              // Arena 數組的最大序列值
        int collides;           //當前cas失敗次數
        int hash;               // 僞隨機自旋 hash值,後面用來自旋
        Object item;            // 當前線程要交換的值
        volatile Object match;  // 要被交換線程的值
        volatile Thread parked; // 要交換的線程,正在阻塞中。交換完成後設置爲Null
    }

    /** The corresponding thread local class */
    static final class Participant extends ThreadLocal<Node> {
        public Node initialValue() { return new Node(); }
    }

exchange : 等待另一個線程到達這個交換點,然後將給定的對象傳輸給它,接收它的對象

  1. 判斷arena (CAS失敗Node)是否有數據,有的話進行arenaExchange否則slotExchange
  2. slotExchange處理中發生了CAS失敗return null,進行arenaExchange處理
public V exchange(V x) throws InterruptedException {
        Object v;
        //null值處理
        Object item = (x == null) ? NULL_ITEM : x; // translate null args
        if ((arena != null ||
             (v = slotExchange(item, false, 0L)) == null) &&
            ((Thread.interrupted() || // disambiguates null return
              (v = arenaExchange(item, false, 0L)) == null)))
            throw new InterruptedException();
        return (v == NULL_ITEM) ? null : (V)v;
    }

slotExchange :交換的核心邏輯

  1. 是否有線程要進行交換數據(Exchanger.slot != null),如果有則進行數據交換,喚醒交換線程
  2. 如果交換數據時CAS操作失敗則返回Null,進行arenaExchange處理
  3. 如果進行等待交換,先進行自旋等待,由SPINS決定次數。自旋結束後再進行park阻塞等待。這時候Exchanger.slot的值就是等待交換線程的Node對象(存儲在ThreadLocal)
  4. 被另一個線程進行喚醒交換數據,執行一系列清空操作,返回已經交換好的數據。交換數據另一個線程已經設置好
private final Object slotExchange(Object item, boolean timed, long ns) {
		//取出初始化的node節點
        Node p = participant.get();
        Thread t = Thread.currentThread();
        if (t.isInterrupted()) // preserve interrupt status so caller can recheck
            return null;
            
		//這個循環是爲了cas失敗在再次進行判斷。比如有1個線程在等待交換,2個線程同時進入for循環準備交換,同一時刻只能一個成功交換,另一個就要進入arena數組中。
        for (Node q;;) {
        	//slot是上一個線程交換的數據
            if ((q = slot) != null) {
            	//q是上一個線程要交換的node
            	//如果有線程在等待交換了,則進行交換
                if (U.compareAndSwapObject(this, SLOT, q, null)) {
                    Object v = q.item;
                    q.match = item;
                    Thread w = q.parked;
                    if (w != null)
                    	//將上一個等待交換的線程調用
                        U.unpark(w);
                    return v;
                }
                // create arena on contention, but continue until slot null
                //如果上面的compareAndSwapObject CAS操作失敗了,則放入arena的競爭節點數組裏。有多個線程同時進行交換cas有可能失敗的。
                if (NCPU > 1 && bound == 0 &&
                    U.compareAndSwapInt(this, BOUND, 0, SEQ))
                    arena = new Node[(FULL + 2) << ASHIFT];
            }
            else if (arena != null)
            	//進入arenaExchange方法
                return null; // caller must reroute to arenaExchange
            else {
            	//線程第一次進來,沒有線程要進行交換。自己就準備進行等待交換
                p.item = item;
                if (U.compareAndSwapObject(this, SLOT, null, p))
                    break;
                p.item = null;
            }
        }

        // await release  等待交換
        int h = p.hash;
        long end = timed ? System.nanoTime() + ns : 0L;
        //自旋次數,NCPU是CPU核數
        int spins = (NCPU > 1) ? SPINS : 1;
        Object v;
        while ((v = p.match) == null) {
        	//自旋等待
            if (spins > 0) {
            	//通過hash操作來判斷自旋,我猜想應該是保證每次交換的節點hash不一樣,保證唯一吧。
                h ^= h << 1; h ^= h >>> 3; h ^= h << 10;
                if (h == 0)
                    h = SPINS | (int)t.getId();
                else if (h < 0 && (--spins & ((SPINS >>> 1) - 1)) == 0)
                    Thread.yield();
            }
            //上面已經設置過U.compareAndSwapObject(this, SLOT, null, p)
            //所以slot!=p是判斷當前要等待交換的節點是不是已經被其它線程交換了。在多線程環境,這種現象是有的。
            else if (slot != p)
                spins = SPINS;
            else if (!t.isInterrupted() && arena == null &&
                     (!timed || (ns = end - System.nanoTime()) > 0L)) {
                U.putObject(t, BLOCKER, this);
                p.parked = t;
                if (slot == p)
                	//當前線程阻塞
                    U.park(false, ns);
                p.parked = null;
                U.putObject(t, BLOCKER, null);
            }
            //如果當前節點已經被其它線程交換走,就設置slot爲空
            else if (U.compareAndSwapObject(this, SLOT, p, null)) {
                v = timed && ns <= 0L && !t.isInterrupted() ? TIMED_OUT : null;
                break;
            }
        }
        //最後的清空操作
        U.putOrderedObject(p, MATCH, null);
        p.item = null;
        p.hash = h;
        //返回另一個線程要交換的數據,v=p.match,另一個線程已經將要交換的數據放入p.match
        return v;
    }

arenaExchange : CAS失敗的交換,也就是arena數組的交換。核心邏輯和slotExchange差不多

  1. 第一個進入arena數組的Node是等待別人交換,這時候arena是沒有數據的。設置arena數據並且進行等待,等待也是先進行自旋等待
  2. 如果arena有數據,則進行數據交換
private final Object arenaExchange(Object item, boolean timed, long ns) {
        Node[] a = arena;
        Node p = participant.get();
        for (int i = p.index;;) {                      // access slot at i
            int b, m, c; long j;                       // j is raw array offset
            //取出arena的數據node,第一個進入arena數組的Node是等待別人交換,這時候arena是沒有數據的
            Node q = (Node)U.getObjectVolatile(a, j = (i << ASHIFT) + ABASE);
            if (q != null && U.compareAndSwapObject(a, j, q, null)) {
            	//進行數據交換
                Object v = q.item;                     // release
                q.match = item;
                Thread w = q.parked;
                if (w != null)
                    U.unpark(w);
                return v;
            }
            else if (i <= (m = (b = bound) & MMASK) && q == null) {
                p.item = item;                         // offer
                //第一次進入arena數組,沒有數據,等待交換並且添加到arena中
                if (U.compareAndSwapObject(a, j, null, p)) {
                    long end = (timed && m == 0) ? System.nanoTime() + ns : 0L;
                    Thread t = Thread.currentThread(); // wait
                    for (int h = p.hash, spins = SPINS;;) {
                        Object v = p.match;
                        if (v != null) {
                        //如果等待的過程中,有線程已經來交換了則進行交換
                            U.putOrderedObject(p, MATCH, null);
                            p.item = null;             // clear for next use
                            p.hash = h;
                            return v;
                        }
                        else if (spins > 0) {
                        //自旋等待
                            h ^= h << 1; h ^= h >>> 3; h ^= h << 10; // xorshift
                            if (h == 0)                // initialize hash
                                h = SPINS | (int)t.getId();
                            else if (h < 0 &&          // approx 50% true
                                     (--spins & ((SPINS >>> 1) - 1)) == 0)
                                Thread.yield();        // two yields per wait
                        }
                        else if (U.getObjectVolatile(a, j) != p)
                            spins = SPINS;       // releaser hasn't set match yet
                        else if (!t.isInterrupted() && m == 0 &&
                                 (!timed ||
                                  (ns = end - System.nanoTime()) > 0L)) {
                            //等待交換
                            U.putObject(t, BLOCKER, this); // emulate LockSupport
                            p.parked = t;              // minimize window
                            if (U.getObjectVolatile(a, j) == p)
                                U.park(false, ns);
                            p.parked = null;
                            U.putObject(t, BLOCKER, null);
                        }
                        else if (U.getObjectVolatile(a, j) == p &&
                                 U.compareAndSwapObject(a, j, p, null)) {
                            //一般是線程中斷或者超時纔會進來
                            if (m != 0)                // try to shrink
                                U.compareAndSwapInt(this, BOUND, b, b + SEQ - 1);
                            p.item = null;
                            p.hash = h;
                            i = p.index >>>= 1;        // descend
                            if (Thread.interrupted())
                                return null;
                            if (timed && m == 0 && ns <= 0L)
                                return TIMED_OUT;
                            break;                     // expired; restart
                        }
                    }
                }
                else
                    p.item = null;                     // clear offer
            }
            else {
                if (p.bound != b) {                    // stale; reset
                    p.bound = b;
                    p.collides = 0;
                    i = (i != m || m == 0) ? m : m - 1;
                }
                else if ((c = p.collides) < m || m == FULL ||
                         !U.compareAndSwapInt(this, BOUND, b, b + SEQ + 1)) {
                    p.collides = c + 1;
                    i = (i == 0) ? m : i - 1;          // cyclically traverse
                }
                else
                    i = m + 1;                         // grow
                p.index = i;
            }
        }
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章