java.util.concurrent*包中的Exchanger類可用於兩個線程之間交換信息
可簡單地將Exchanger對象理解爲一個包含兩個格子的容器,通過exchanger方法可以向兩個格子中填充信息。當兩個格子中的均被填充時,該對象會自動將兩個格子的信息交換,然後返回給線程,從而實現兩個線程的信息交換。
源碼:
構造函數
public Exchanger() {
//Participant 是一個ThreadLocal子類,初始化一個Node節點
participant = new Participant();
}
Node節點:存儲交換數據的節點。
- 每個線程都有一個自己的Node,存儲在Participant即ThreadLocal對象當中
- 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 : 等待另一個線程到達這個交換點,然後將給定的對象傳輸給它,接收它的對象
- 判斷arena (CAS失敗Node)是否有數據,有的話進行arenaExchange否則slotExchange
- 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 :交換的核心邏輯
- 是否有線程要進行交換數據(Exchanger.slot != null),如果有則進行數據交換,喚醒交換線程
- 如果交換數據時CAS操作失敗則返回Null,進行arenaExchange處理
- 如果進行等待交換,先進行自旋等待,由SPINS決定次數。自旋結束後再進行park阻塞等待。這時候Exchanger.slot的值就是等待交換線程的Node對象(存儲在ThreadLocal)
- 被另一個線程進行喚醒交換數據,執行一系列清空操作,返回已經交換好的數據。交換數據另一個線程已經設置好
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差不多
- 第一個進入arena數組的Node是等待別人交換,這時候arena是沒有數據的。設置arena數據並且進行等待,等待也是先進行自旋等待
- 如果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;
}
}
}