Exchanger原始碼解析

寒武沒有紀發表於2018-10-10

Exchanger(交換者)是一個用於執行緒間協作的工具類。Exchanger 用於進行執行緒間的資料交換。它提供一個同步點,在這個同步點,兩個執行緒可以交換彼此的資料。這兩個執行緒通過exchange 方法交換資料,如果第一個執行緒先執行 exchange() 方法,它會一直等待第二個執行緒也執行 exchange 方法,當兩個執行緒都到達同步點時,這兩個執行緒就可以交換資料,將本執行緒生產出來的資料傳遞給對方。

// 用於左移Node陣列下標,以便得出陣列在記憶體中偏移量來獲取資料,避免偽共享
private static final int ASHIFT = 7;

// Node陣列最大下標
private static final int MMASK = 0xff;

// 用於遞增bound,每次遞增一個SEQ
private static final int SEQ = MMASK + 1;

// CPU核心數
private static final int NCPU = Runtime.getRuntime().availableProcessors();

// 當前陣列最大下標
static final int FULL = (NCPU >= (MMASK << 1)) ? MMASK : NCPU >>> 1;

// 自旋次數,CPU核心為1時自旋會被禁用
private static final int SPINS = 1 << 10;

// 用於exchange方法中引數為null時傳遞給其它執行緒的物件
private static final Object NULL_ITEM = new Object();

// 用於超時傳遞的物件
private static final Object TIMED_OUT = new Object();

// 節點用於保持需要交換的資料
@sun.misc.Contended static final class Node {
    int index;              // arana陣列下標,多槽位時使用
    int bound;              // 上一次記錄的bound 
    int collides;           // CAS失敗次數
    int hash;               // 用於自旋的偽隨機數
    Object item;            // 當前執行緒需要交換的資料
    volatile Object match;  // 匹配執行緒交換的資料
    volatile Thread parked; // 記錄當前掛起的執行緒
}

// 使用者記錄執行緒狀態的內部類
static final class Participant extends ThreadLocal<Node> {
    public Node initialValue() { return new Node(); }
}

// 記錄執行緒狀態
private final Participant participant;

// 多槽位資料交換使用
private volatile Node[] arena;

// 用於交換資料的槽位
private volatile Node slot;

/**
 * The index of the largest valid arena position, OR'ed with SEQ
 * number in high bits, incremented on each update.  The initial
 * update from 0 to SEQ is used to ensure that the arena array is
 * constructed only once.
 */
private volatile int bound;

exchange 方法

public V exchange(V x) throws InterruptedException {
    Object v;
    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;
}
private final Object slotExchange(Object item, boolean timed, long ns) {
    Node p = participant.get(); // 獲取當前節點物件
    Thread t = Thread.currentThread(); // 當前執行緒
    // 執行緒中斷,直接返回null
    if (t.isInterrupted()) // preserve interrupt status so caller can recheck
        return null;
    // 自旋
    for (Node q;;) {
        // 槽位slot不為null,則說明已經存線上程等待交換資料
        if ((q = slot) != null) {
            // CAS置空槽位slot
            if (U.compareAndSwapObject(this, SLOT, q, null)) {
                Object v = q.item; // 獲取槽位中需要交換的物件
                q.match = item; // 將當前需要交換的資料設定到match中
                Thread w = q.parked; // 獲取被掛起執行緒
                // 存在掛起執行緒,則喚醒
                if (w != null)
                    U.unpark(w); 
                return v; // 返回交換後的資料
            }
            // 存在競爭,其它執行緒搶先一步,需要使用多槽位交換方式
            // CPU為多核心 且 bound等於0(arana陣列未初始化),則CAS操作將bound增加SEQ
            if (NCPU > 1 && bound == 0 &&
                U.compareAndSwapInt(this, BOUND, 0, SEQ))
                arena = new Node[(FULL + 2) << ASHIFT]; // 初始化arana陣列
        }
        else if (arena != null) // 多槽位不為空,執行多槽位交換
            return null; // caller must reroute to arenaExchange
        else {
            // 表示當前執行緒是第一個執行緒進來交換資料 或 之前交換任務已完成,可重新認為是第一個執行緒,
            // 將需要交換的資料存放到槽位slot的item屬性
            p.item = item;
            // CAS設定槽位為p
            if (U.compareAndSwapObject(this, SLOT, null, p))
                break; // CAS操作成功結束自旋
            p.item = null; // CAS設定槽位失敗,置空item,繼續自旋操作
        }
    }

    // 當前執行緒已經佔據槽位,等待其它執行緒交換資料
    int h = p.hash;
    long end = timed ? System.nanoTime() + ns : 0L;
    int spins = (NCPU > 1) ? SPINS : 1; // 自旋次數
    Object v;
    // 其它執行緒成功交換槽位中資料
    while ((v = p.match) == null) {
        if (spins > 0) { // 自旋
            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(); // 執行緒讓步,提供CPU利用率
        }
        // 存線上程交換資料,已修改槽位slot,但未修改match屬性,則等待
        else if (slot != p)
            spins = SPINS;
        // 執行緒未中斷 且 不是多槽位交換 且 (沒有設定超時 或 超時時間未到)
        else if (!t.isInterrupted() && arena == null &&
                 (!timed || (ns = end - System.nanoTime()) > 0L)) {
            U.putObject(t, BLOCKER, this); // 設定執行緒t被當前物件阻塞
            p.parked = t; // 設定節點掛起執行緒屬性parked
            // 如果槽位slot不等於null,表明還沒有執行緒與之交換資料,則將當前執行緒掛起
            if (slot == p) 
                U.park(false, ns);
            p.parked = null; // 執行緒被喚醒,將節點掛起執行緒parked屬性設定為null
            U.putObject(t, BLOCKER, null); // 設定執行緒t沒有被任何物件阻塞
        }
        // 不滿足上述新增,交換失敗,重置槽位slot
        else if (U.compareAndSwapObject(this, SLOT, p, null)) {
            v = timed && ns <= 0L && !t.isInterrupted() ? TIMED_OUT : null;
            break;
        }
    }
    U.putOrderedObject(p, MATCH, null); // 置空match
    p.item = null; // 置空item
    p.hash = h;
    return v; // 返回交換資料
}

相關文章