1 前言
在JDK的并發(fā)包里提供了幾個(gè)非常有用的并發(fā)工具類。CountDownLatch、CyclicBarrier和 Semaphore工具類提供了一種并發(fā)流程控制的手段,Exchanger工具類則提供了在線程間交換數(shù) 據(jù)的一種手段。本文會(huì)對(duì)這些并發(fā)工具類進(jìn)行介紹。
2 等待多線程完成的CountDownLatch
2.1 CountDownLatch案例演示
countdownlatch 是一個(gè)同步工具類,它允許一個(gè)或多個(gè)線程一直等待,直到其他線程的操作執(zhí)行完畢再執(zhí)行。從命名可以解讀到 countdown 是倒數(shù)的意思,類似于我們倒計(jì)時(shí)的概念。
countdownlatch 提供了兩個(gè)方法,一個(gè)是 countDown,一個(gè)是 await,countdownlatch 初始化的時(shí)候需要傳入一個(gè)整數(shù),在這個(gè)整數(shù)倒數(shù)到 0 之前,調(diào)用了 await 方法的程序都必須要等待,然后通過(guò) countDown 來(lái)倒數(shù)。
現(xiàn)在有一個(gè)需求就是:學(xué)校放學(xué)了,等學(xué)生們?nèi)孔咄曛笤訇P(guān)閉教室的門,如果不用CountDownLatch的話代碼如下:
public class WithoutCountDownLatchDemo {
public static void main(String[] args) {
for (int i = 0; i < 6; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "同學(xué)離開(kāi)了");
}).start();
}
System.out.println(Thread.currentThread().getName() + "要關(guān)門了,此時(shí)教室已經(jīng)沒(méi)有人了~");
}
}
輸出結(jié)果如下:
Thread-0同學(xué)離開(kāi)了
main要關(guān)門了,此時(shí)教室已經(jīng)沒(méi)有人了~
Thread-1同學(xué)離開(kāi)了
Thread-2同學(xué)離開(kāi)了
Thread-3同學(xué)離開(kāi)了
Thread-4同學(xué)離開(kāi)了
Thread-5同學(xué)離開(kāi)了
同學(xué)還沒(méi)有走光,但是門卻先關(guān)了,這樣顯然是不對(duì)的,那么使用了CountDownLatch的話代碼是怎樣的呢?
public class CountDownLatchDemo {
public static void main(String[] args) {
CountDownLatch countDownLatch = new CountDownLatch(6);
for (int i = 0; i < 6; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName()+"同學(xué)離開(kāi)了");
countDownLatch.countDown();
}).start();
}
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"要關(guān)門了,此時(shí)教室已經(jīng)沒(méi)人了~");
}
}
運(yùn)行結(jié)果如下:
Thread-0同學(xué)離開(kāi)了
Thread-2同學(xué)離開(kāi)了
Thread-1同學(xué)離開(kāi)了
Thread-3同學(xué)離開(kāi)了
Thread-5同學(xué)離開(kāi)了
Thread-4同學(xué)離開(kāi)了
main要關(guān)門了,此時(shí)教室已經(jīng)沒(méi)人了~
等到同學(xué)們?nèi)孔咄曛螅砰_(kāi)始關(guān)門,這樣才是正確的!
從如下代碼中:
public class CountDownLatchDemo2 {
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch=new CountDownLatch(3);
new Thread(() -> {
System.out.println(""+Thread.currentThread().getName()+"-執(zhí)行中");
countDownLatch.countDown();
System.out.println(""+Thread.currentThread().getName()+"-執(zhí)行完畢");
},"t1").start();
new Thread(()->{
System.out.println(""+Thread.currentThread().getName()+"-執(zhí)行中");
countDownLatch.countDown();
System.out.println(""+Thread.currentThread().getName()+"-執(zhí)行完畢");
},"t2").start();
new Thread(()->{
System.out.println(""+Thread.currentThread().getName()+"-執(zhí)行中");
countDownLatch.countDown();
System.out.println(""+Thread.currentThread().getName()+"-執(zhí)行完畢");
},"t3").start();
countDownLatch.await();
System.out.println("所有線程執(zhí)行完畢");
}
}
可以看出有點(diǎn)類似 join 的功能,但是比 join 更加靈活。CountDownLatch 構(gòu)造函數(shù)會(huì)接收一個(gè) int 類型的參數(shù)作為計(jì)數(shù)器的初始值,當(dāng)調(diào)用 CountDownLatch 的countDown 方法時(shí),這個(gè)計(jì)數(shù)器就會(huì)減一。通過(guò) await 方法去阻塞去阻塞主流程。
2.2 CountDownLatch源碼分析
2.2.1 CountDownLatch類圖
對(duì)于 CountDownLatch,我們僅僅需要關(guān)心兩個(gè)方法,一個(gè)是 countDown() 方法,另一個(gè)是 await() 方法。countDown() 方法每次調(diào)用都會(huì)將 state 減 1,直到state 的值為 0;而 await 是一個(gè)阻塞方法,當(dāng) state 減 為 0 的時(shí)候,await 方法才會(huì)返回。await 可以被多個(gè)線程調(diào)用,大家在這個(gè)時(shí)候腦子里要有個(gè)圖:所有調(diào)用了await 方法的線程阻塞在 AQS 的阻塞隊(duì)列中,等待條件滿(state == 0),將線程從隊(duì)列中一個(gè)個(gè)喚醒過(guò)來(lái)。
2.2.2 acquireSharedInterruptibly
countdownlatch 也用到了 AQS,在 CountDownLatch 內(nèi)部寫了一個(gè) Sync 并且繼承了 AQS 這個(gè)抽象類重寫了 AQS中的共享鎖方法。首先看到下面這個(gè)代碼,這塊代碼主要是判斷當(dāng)前線程是否獲取到了共享鎖 ; ( 在CountDownLatch 中 , 使 用 的 是 共 享 鎖 機(jī) 制 ,因?yàn)镃ountDownLatch 并不需要實(shí)現(xiàn)互斥的特性)
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
//state 如果不等于 0,說(shuō)明當(dāng)前線程需要加入到共享鎖隊(duì)列中
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
2.2.3 doAcquireSharedInterruptibly
- addWaiter 設(shè)置為 shared 模式
- tryAcquire 和 tryAcquireShared 的返回值不同,因此會(huì)多出一個(gè)判斷過(guò)程
- 在 判 斷 前 驅(qū) 節(jié) 點(diǎn) 是 頭 節(jié) 點(diǎn) 后 , 調(diào) 用 了setHeadAndPropagate 方法,而不是簡(jiǎn)單的更新一下頭節(jié)點(diǎn)。
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
final Node node = addWaiter(Node.SHARED);
//創(chuàng)建一個(gè)共享模式的節(jié)點(diǎn)添加到隊(duì)列中
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head) {
// 就判斷嘗試獲取鎖
int r = tryAcquireShared(arg);
//r>=0 表示獲取到了執(zhí)行權(quán)限,這個(gè)時(shí)候因?yàn)?state!=0,所以不會(huì)執(zhí)行這段代碼
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return;
}
}
//阻塞線程
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
2.2.4 圖解分析
加入這個(gè)時(shí)候有 3 個(gè)線程調(diào)用了 await 方法,由于這個(gè)時(shí)候 state 的值還不為 0,所以這三個(gè)線程都會(huì)加入到 AQS隊(duì)列中。并且三個(gè)線程都處于阻塞狀態(tài)。
2.2.5 CountDownLatch.countDown
由于線程被 await 方法阻塞了,所以只有等到countdown 方法使得 state=0 的時(shí)候才會(huì)被喚醒,我們來(lái)看看 countdown 做了什么
- 只有當(dāng) state 減為 0 的時(shí)候,tryReleaseShared 才返回 true, 否則只是簡(jiǎn)單的 state = state - 1
- 如果 state=0, 則調(diào)用 doReleaseShared 喚醒處于 await 狀態(tài)下的線程
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
CountDownLatch中的tryReleaseShared
protected boolean tryReleaseShared(int releases) {
// Decrement count; signal when transition to zero
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c-1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
}
AQS.doReleaseShared
共享鎖的釋放和獨(dú)占鎖的釋放有一定的差別,前面喚醒鎖的邏輯和獨(dú)占鎖是一樣,先判斷頭結(jié)點(diǎn)是不是SIGNAL 狀態(tài),如果是,則修改為 0,并且喚醒頭結(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)。
PROPAGATE: 標(biāo)識(shí)為 PROPAGATE 狀態(tài)的節(jié)點(diǎn),是共享鎖模式下的節(jié)點(diǎn)狀態(tài),處于這個(gè)狀態(tài)下的節(jié)點(diǎn),會(huì)對(duì)線程的喚醒進(jìn)行傳播。
private void doReleaseShared() {
/*
* Ensure that a release propagates, even if there are other
* in-progress acquires/releases. This proceeds in the usual
* way of trying to unparkSuccessor of head if it needs
* signal. But if it does not, status is set to PROPAGATE to
* ensure that upon release, propagation continues.
* Additionally, we must loop in case a new node is added
* while we are doing this. Also, unlike other uses of
* unparkSuccessor, we need to know if CAS to reset status
* fails, if so rechecking.
*/
for (;;) {
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
if (ws == Node.SIGNAL) {
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
unparkSuccessor(h);
}
// 這個(gè) CAS 失敗的場(chǎng)景是:執(zhí)行到這里的時(shí)候,剛好有一個(gè)節(jié)點(diǎn)入隊(duì),入隊(duì)會(huì)將這個(gè) ws 設(shè)置為 -1
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
// 如果到這里的時(shí)候,前面喚醒的線程已經(jīng)占領(lǐng)了 head,那么再循環(huán)
// 通過(guò)檢查頭節(jié)點(diǎn)是否改變了,如果改變了就繼續(xù)循環(huán)
if (h == head) // loop if head changed
break;
}
}
h == head:說(shuō)明頭節(jié)點(diǎn)還沒(méi)有被剛剛用unparkSuccessor 喚醒的線程(這里可以理解為ThreadB)占有,此時(shí) break 退出循環(huán)。
h != head:頭節(jié)點(diǎn)被剛剛喚醒的線程(這里可以理解為ThreadB)占有,那么這里重新進(jìn)入下一輪循環(huán),喚醒下一個(gè)節(jié)點(diǎn)(這里是 ThreadB )。我們知道,等到ThreadB 被喚醒后,其實(shí)是會(huì)主動(dòng)喚醒 ThreadC...
doAcquireSharedInterruptibly
一旦 ThreadA 被喚醒,代碼又會(huì)繼續(xù)回到doAcquireSharedInterruptibly 中來(lái)執(zhí)行。如果當(dāng)前 state滿足=0 的條件,則會(huì)執(zhí)行 setHeadAndPropagate 方法
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
final Node node = addWaiter(Node.SHARED);
//創(chuàng)建一個(gè)共享模式的節(jié)點(diǎn)添加到隊(duì)列中
boolean failed = true;
try {
for (;;) {//被喚醒的線程進(jìn)入下一次循環(huán)繼續(xù)判斷
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
setHeadAndPropagate
這個(gè)方法的主要作用是把被喚醒的節(jié)點(diǎn),設(shè)置成 head 節(jié) 點(diǎn)。 然后繼續(xù)喚醒隊(duì)列中的其他線程。由于現(xiàn)在隊(duì)列中有 3 個(gè)線程處于阻塞狀態(tài),一旦 ThreadA被喚醒,并且設(shè)置為 head 之后,會(huì)繼續(xù)喚醒后續(xù)的ThreadB
private void setHeadAndPropagate(Node node, int propagate) {
Node h = head; // Record old head for check below
setHead(node);
/*
* Try to signal next queued node if:
* Propagation was indicated by caller,
* or was recorded (as h.waitStatus either before
* or after setHead) by a previous operation
* (note: this uses sign-check of waitStatus because
* PROPAGATE status may transition to SIGNAL.)
* and
* The next node is waiting in shared mode,
* or we don't know, because it appears null
*
* The conservatism in both of these checks may cause
* unnecessary wake-ups, but only when there are multiple
* racing acquires/releases, so most need signals now or soon
* anyway.
*/
if (propagate > 0 || h == null || h.waitStatus < 0 ||
(h = head) == null || h.waitStatus < 0) {
Node s = node.next;
if (s == null || s.isShared())
doReleaseShared();
}
}
圖解分析
3 同步屏障CyclicBarrier
CyclicBarrier的字面意思是可循環(huán)使用(Cyclic)的屏障(Barrier)。它要做的事情是,讓一 組線程到達(dá)一個(gè)屏障(也可以叫同步點(diǎn))時(shí)被阻塞,直到最后一個(gè)線程到達(dá)屏障時(shí),屏障才會(huì) 開(kāi)門,所有被屏障攔截的線程才會(huì)繼續(xù)運(yùn)行。
3.1 CyclicBarrier使用場(chǎng)景
使用場(chǎng)景:5個(gè)工程師一起來(lái)公司應(yīng)聘,招聘方式分為筆試和面試。首先,要等人到齊后,開(kāi)始筆試;筆試結(jié)束之后,再一起參加面試。把5個(gè)人看作5個(gè)線程,代碼如下:
Main類:
public class Main {
public static void main(String[] args) throws BrokenBarrierException, InterruptedException {
CyclicBarrier barrier = new CyclicBarrier(5);
for (int i = 0; i < 5; i++) {
new MyThread("線程-" + (i + 1), barrier).start();
}
}
}
MyThread類:
public class MyThread extends Thread{
private final CyclicBarrier barrier;
private final Random random = new Random();
public MyThread(String name, CyclicBarrier barrier) {
super(name);
this.barrier = barrier;
}
@Override public void run() {
try {
Thread.sleep(random.nextInt(2000));
System.out.println(Thread.currentThread().getName() + " - 已經(jīng)到達(dá)公司");
barrier.await();
Thread.sleep(random.nextInt(2000));
System.out.println(Thread.currentThread().getName() + " - 已經(jīng)筆試結(jié)束");
barrier.await();
Thread.sleep(random.nextInt(2000));
System.out.println(Thread.currentThread().getName() + " - 已經(jīng)面試結(jié)束");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
super.run();
}
}
在整個(gè)過(guò)程中,有2個(gè)同步點(diǎn):第1個(gè)同步點(diǎn),要等所有應(yīng)聘者都到達(dá)公司,再一起開(kāi)始筆試;第2個(gè)同步點(diǎn),要等所有應(yīng)聘者都結(jié)束筆試,之后一起進(jìn)入面試環(huán)節(jié)。
3.2 CyclicBarrier實(shí)現(xiàn)原理
CyclicBarrier基于ReentrantLock+Condition實(shí)現(xiàn)。
public class CyclicBarrier {
private final ReentrantLock lock = new ReentrantLock();
// 用于線程之間相互喚醒
private final Condition trip = lock.newCondition();
// 線程總數(shù)
private final int parties;
private int count;
private Generation generation = new Generation();
// ...
}
下面詳細(xì)介紹 CyclicBarrier 的實(shí)現(xiàn)原理。先看構(gòu)造方法:
public CyclicBarrier(int parties, Runnable barrierAction) {
if (parties <= 0) throw new IllegalArgumentException();
// 參與方數(shù)量
this.parties = parties;
this.count = parties;
// 當(dāng)所有線程被喚醒時(shí),執(zhí)行barrierCommand表示的Runnable。
this.barrierCommand = barrierAction;
}
接下來(lái)看一下await()方法的實(shí)現(xiàn)過(guò)程。
public int await() throws InterruptedException, BrokenBarrierException {
try {
return dowait(false, 0L);
} catch (TimeoutException toe) {
throw new Error(toe); // cannot happen
}
}
private int dowait(boolean timed, long nanos)
throws InterruptedException, BrokenBarrierException,
TimeoutException {
final ReentrantLock lock = this.lock;
lock.lock();
try {
final Generation g = generation;
if (g.broken)
throw new BrokenBarrierException();
// 響應(yīng)中斷
if (Thread.interrupted()) {
// 喚醒所有阻塞的線程
breakBarrier();
throw new InterruptedException();
}
// 每個(gè)線程調(diào)用一次await(),count都要減1
int index = --count;
// 當(dāng)count減到0的時(shí)候,此線程喚醒其他所有線程
if (index == 0) { // tripped
boolean ranAction = false;
try {
final Runnable command = barrierCommand;
if (command != null)
command.run();
ranAction = true;
nextGeneration();
return 0;
} finally {
if (!ranAction)
breakBarrier();
}
}
// loop until tripped, broken, interrupted, or timed out
for (;;) {
try {
if (!timed)
trip.await();
else if (nanos > 0L)
nanos = trip.awaitNanos(nanos);
} catch (InterruptedException ie) {
if (g == generation && ! g.broken) {
breakBarrier();
throw ie;
} else {
// We're about to finish waiting even if we had not
// been interrupted, so this interrupt is deemed to
// "belong" to subsequent execution.
Thread.currentThread().interrupt();
}
}
if (g.broken)
throw new BrokenBarrierException();
if (g != generation)
return index;
if (timed && nanos <= 0L) {
breakBarrier();
throw new TimeoutException();
}
}
} finally {
lock.unlock();
}
}
private void breakBarrier() {
generation.broken = true;
count = parties;
trip.signalAll();
}
private void nextGeneration() {
// signal completion of last generation
trip.signalAll();
// set up next generation
count = parties;
generation = new Generation();
}
以上幾點(diǎn)的說(shuō)明:
- CyclicBarrier是可以被重用的。以上一節(jié)的應(yīng)聘場(chǎng)景為例,來(lái)了5個(gè)線程,這5個(gè)線程互相等待,到齊后一起被喚醒,各自執(zhí)行接下來(lái)的邏輯;然后,這5個(gè)線程繼續(xù)互相等待,到齊后再一起被喚醒。每一輪被稱為一個(gè)Generation,就是一次同步點(diǎn)。
- CyclicBarrier 會(huì)響應(yīng)中斷。5 個(gè)線程沒(méi)有到齊,如果有線程收到了中斷信號(hào),所有阻塞的線程也會(huì)被喚醒,就是上面的breakBarrier()方法。然后count被重置為初始值(parties),重新開(kāi)始。
- 上面的回調(diào)方法,barrierAction只會(huì)被第5個(gè)線程執(zhí)行1次(在喚醒其他4個(gè)線程之前),而不是5個(gè)線程每個(gè)都執(zhí)行1次。
3.3 CyclicBarrier與CountDownLatch 區(qū)別
CountDownLatch 是一次性的,CyclicBarrier 是可循環(huán)利用的
CountDownLatch的計(jì)數(shù)器只能使用一次,而CyclicBarrier的計(jì)數(shù)器可以使用reset()方法重置,可以使用多次,所以CyclicBarrier能夠處理更為復(fù)雜的場(chǎng)景;
4 控制并發(fā)線程數(shù)的Semaphore
Semaphore(信號(hào)量)是用來(lái)控制同時(shí)訪問(wèn)特定資源的線程數(shù)量,它通過(guò)協(xié)調(diào)各個(gè)線程,以 保證合理的使用公共資源
4.1 Semaphore的使用場(chǎng)景
public class SemaphoreDemo {
public static void main(String[] args) {
Semaphore semaphore=new Semaphore(3);//此時(shí)海底撈有3個(gè)空桌
for (int i = 0; i < 6; i++) {
new Thread(() -> {
try {
semaphore.acquire();
System.out.println("第"+Thread.currentThread().getName()+"等待者搶到座位。");
//假設(shè)每桌客人吃飯時(shí)間為3S
TimeUnit.SECONDS.sleep(3);
System.out.println("第"+Thread.currentThread().getName()+"客人吃完飯離開(kāi)。");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release();
}
},String.valueOf(i)).start();
}
}
}
運(yùn)行結(jié)果如下:
第0等待者搶到座位。
第1等待者搶到座位。
第2等待者搶到座位。
第1客人吃完飯離開(kāi)。
第0客人吃完飯離開(kāi)。
第2客人吃完飯離開(kāi)。
第4等待者搶到座位。
第5等待者搶到座位。
第3等待者搶到座位。
第5客人吃完飯離開(kāi)。
第3客人吃完飯離開(kāi)。
第4客人吃完飯離開(kāi)。
4.2 Semaphore源碼分析
假設(shè)有n個(gè)線程來(lái)獲取Semaphore里面的10份資源(n > 10),n個(gè)線程中只有10個(gè)線程能獲取到,其他線程都會(huì)阻塞。直到有線程釋放了資源,其他線程才能獲取到。
當(dāng)初始的資源個(gè)數(shù)為1的時(shí)候,Semaphore退化為排他鎖。正因?yàn)槿绱耍琒emaphone的實(shí)現(xiàn)原理和鎖十分類似,是基于AQS,有公平和非公平之分。Semaphore相關(guān)類的繼承體系如下圖所示:
創(chuàng)建 Semaphore 實(shí)例的時(shí)候,需要一個(gè)參數(shù) permits,這個(gè)基本上可以確定是設(shè)置給 AQS 的 state 的,然后每個(gè)線程調(diào)用 acquire 的時(shí)候,執(zhí)行 state = state - 1,release 的時(shí)候執(zhí)行 state = state + 1,當(dāng)然,acquire 的時(shí)候,如果 state = 0,說(shuō)明沒(méi)有資源了,需要等待其他線程 release。
Semaphore 分公平策略和非公平策略
4.2.1 FairSync
/**
* Fair version
*/
static final class FairSync extends Sync {
private static final long serialVersionUID = 2014338818796000944L;
FairSync(int permits) {
super(permits);
}
protected int tryAcquireShared(int acquires) {
for (;;) {
// 區(qū)別就在于是不是會(huì)先判斷是否有線程在排隊(duì),然后才進(jìn)行 CAS 減操作
if (hasQueuedPredecessors())
return -1;
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
}
4.2.2 NofairSync
通過(guò)對(duì)比發(fā)現(xiàn)公平和非公平的區(qū)別就在于是否多了一個(gè)hasQueuedPredecessors 的判斷
/**
* NonFair version
*/
static final class NonfairSync extends Sync {
private static final long serialVersionUID = -2694183684443567898L;
NonfairSync(int permits) {
super(permits);
}
protected int tryAcquireShared(int acquires) {
return nonfairTryAcquireShared(acquires);
}
}
final int nonfairTryAcquireShared(int acquires) {
for (;;) {
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
后面的代碼和 CountDownLatch 的是完全一樣,都是基于共享鎖的實(shí)現(xiàn)。
5 線程間交換數(shù)據(jù)的Exchanger
Exchanger(交換者)是一個(gè)用于線程間協(xié)作的工具類。Exchanger用于進(jìn)行線程間的數(shù)據(jù)交 換。它提供一個(gè)同步點(diǎn),在這個(gè)同步點(diǎn),兩個(gè)線程可以交換彼此的數(shù)據(jù)。這兩個(gè)線程通過(guò) exchange方法交換數(shù)據(jù),如果第一個(gè)線程先執(zhí)行exchange()方法,它會(huì)一直等待第二個(gè)線程也 執(zhí)行exchange方法,當(dāng)兩個(gè)線程都到達(dá)同步點(diǎn)時(shí),這兩個(gè)線程就可以交換數(shù)據(jù),將本線程生產(chǎn) 出來(lái)的數(shù)據(jù)傳遞給對(duì)方。
5.1 使用場(chǎng)景
Exchanger用于線程之間交換數(shù)據(jù),代碼如下:
public class ExchangerDemo {
private static final Random random = new Random();
public static void main(String[] args) {
// 建一個(gè)多線程共用的exchange對(duì)象
// 把exchange對(duì)象傳給3個(gè)線程對(duì)象。每個(gè)線程在自己的run方法中調(diào)用exchange,把自 己的數(shù)據(jù)作為參數(shù)
// 傳遞進(jìn)去,返回值是另外一個(gè)線程調(diào)用exchange傳進(jìn)去的參數(shù)
Exchanger<String> exchanger = new Exchanger<>();
new Thread("線程1") {
@Override
public void run() {
while (true) {
try {
// 如果沒(méi)有其他線程調(diào)用exchange,線程阻塞,直到有其他線程調(diào) 用exchange為止。
String otherData = exchanger.exchange("交換數(shù)據(jù)1");
System.out.println(Thread.currentThread().getName() + "得到<==" + otherData);
Thread.sleep(random.nextInt(2000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
new Thread("線程2") {
@Override
public void run() {
while (true) {
try {
String otherData = exchanger.exchange("交換數(shù)據(jù)2");
System.out.println(Thread.currentThread().getName() + "得到<==" + otherData);
Thread.sleep(random.nextInt(2000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
new Thread("線程3") {
@Override
public void run() {
while (true) {
try {
String otherData = exchanger.exchange("交換數(shù)據(jù)3");
System.out.println(Thread.currentThread().getName() + "得到<==" + otherData);
Thread.sleep(random.nextInt(2000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
}
}
在上面的例子中,3個(gè)線程并發(fā)地調(diào)用exchange(...),會(huì)兩兩交互數(shù)據(jù),如1/2、1/3和2/3。
5.2 Exchanger實(shí)現(xiàn)原理
Exchanger的核心機(jī)制和Lock一樣,也是CAS+park/unpark。
5.2.1 Exchanger內(nèi)部代碼
首先,在Exchanger內(nèi)部,有兩個(gè)內(nèi)部類:Participant和Node,代碼如下:
public class Exchanger<V> {
//...
// 添加了Contended注解,表示偽共享與緩存行填充
@sun.misc.Contended static final class Node {
int index; // Arena index
int bound; // Last recorded value of Exchanger.bound
int collides; // 本次綁定中,CAS操作失敗次數(shù)
int hash; // 自旋偽隨機(jī)
Object item; // 本線程要交換的數(shù)據(jù)
volatile Object match; // 對(duì)方線程交換來(lái)的數(shù)據(jù)
// 當(dāng)前線程
volatile Thread parked; // 當(dāng)前線程阻塞的時(shí)候設(shè)置該屬性,不阻塞為null。
}
/** The corresponding thread local class */
static final class Participant extends ThreadLocal<java.util.concurrent.Exchanger.Node> {
public java.util.concurrent.Exchanger.Node initialValue() { return new java.util.concurrent.Exchanger.Node(); }
}
//...
}
每個(gè)線程在調(diào)用exchange(...)方法交換數(shù)據(jù)的時(shí)候,會(huì)先創(chuàng)建一個(gè)Node對(duì)象。
這個(gè)Node對(duì)象就是對(duì)該線程的包裝,里面包含了3個(gè)重要字段:第一個(gè)是該線程要交互的數(shù)據(jù),第二個(gè)是對(duì)方線程交換來(lái)的數(shù)據(jù),最后一個(gè)是該線程自身。
一個(gè)Node只能支持2個(gè)線程之間交換數(shù)據(jù),要實(shí)現(xiàn)多個(gè)線程并行地交換數(shù)據(jù),需要多個(gè)Node,因此在Exchanger里面定義了Node數(shù)組:
/**
* Elimination array; null until enabled (within slotExchange).
* Element accesses use emulation of volatile gets and CAS.
*/
private volatile Node[] arena;
5.2.2 exchange(V x)實(shí)現(xiàn)分析
明白了大致思路,下面來(lái)看exchange(V x)方法的詳細(xì)實(shí)現(xiàn):
@SuppressWarnings("unchecked")
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;
}
上面方法中,如果arena不是null,表示啟用了arena方式交換數(shù)據(jù)。如果arena不是null,并且線程被中斷,則拋異常
如果arena不是null,并且arenaExchange的返回值為null,則拋異常。對(duì)方線程交換來(lái)的null值是封裝為NULL_ITEM對(duì)象的,而不是null。
如果slotExchange的返回值是null,并且線程被中斷,則拋異常。
如果slotExchange的返回值是null,并且areaExchange的返回值是null,則拋異常。
slotExchange的實(shí)現(xiàn):
/**
* Exchange function used until arenas enabled. See above for explanation.
* 如果不啟用arenas,則使用該方法進(jìn)行線程間數(shù)據(jù)交換。
* @param item 需要交換的數(shù)據(jù)
* @param timed 是否是計(jì)時(shí)等待,true表示是計(jì)時(shí)等待
* @param ns 如果是計(jì)時(shí)等待,該值表示最大等待的時(shí)長(zhǎng)。
* @return 對(duì)方線程交換來(lái)的數(shù)據(jù);如果等待超時(shí)或線程中斷,或者啟用了arena,則返回 null。
*/
private final Object slotExchange(Object item, boolean timed, long ns) {
// participant在初始化的時(shí)候設(shè)置初始值為new Node()
// 獲取本線程要交換的數(shù)據(jù)節(jié)點(diǎn)
Node p = participant.get();
// 獲取當(dāng)前線程
Thread t = Thread.currentThread();
// 如果線程被中斷,則返回null。
if (t.isInterrupted()) // preserve interrupt status so caller can recheck
return null;
for (Node q;;) {
// 如果slot非空,表明有其他線程在等待該線程交換數(shù)據(jù)
if ((q = slot) != null) {
// CAS操作,將當(dāng)前線程的slot由slot設(shè)置為null
// 如果操作成功,則執(zhí)行if中的語(yǔ)句
if (U.compareAndSwapObject(this, SLOT, q, null)) {
// 獲取對(duì)方線程交換來(lái)的數(shù)據(jù)
Object v = q.item;
// 設(shè)置要交換的數(shù)據(jù)
q.match = item;
// 獲取q中阻塞的線程對(duì)象
Thread w = q.parked;
if (w != null)
// 如果對(duì)方阻塞的線程非空,則喚醒阻塞的線程
U.unpark(w);
return v;
}
// create arena on contention, but continue until slot null
// 創(chuàng)建arena用于處理多個(gè)線程需要交換數(shù)據(jù)的場(chǎng)合,防止slot沖突
if (NCPU > 1 && bound == 0 &&
U.compareAndSwapInt(this, BOUND, 0, SEQ))
arena = new Node[(FULL + 2) << ASHIFT];
}
// 如果arena不是null,需要調(diào)用者調(diào)用arenaExchange方法接著獲取對(duì)方線程交 換來(lái)的數(shù)據(jù)
else if (arena != null)
return null; // caller must reroute to arenaExchange
else {
// 如果slot為null,表示對(duì)方?jīng)]有線程等待該線程交換數(shù)據(jù)
// 設(shè)置要交換的本方數(shù)據(jù)
p.item = item;
// 設(shè)置當(dāng)前線程要交換的數(shù)據(jù)到slot
// CAS操作,如果設(shè)置失敗,則進(jìn)入下一輪for循環(huán)
if (U.compareAndSwapObject(this, SLOT, null, p))
break;
p.item = null;
}
}
// await release
// 沒(méi)有對(duì)方線程等待交換數(shù)據(jù),將當(dāng)前線程要交換的數(shù)據(jù)放到slot中,是一個(gè)Node對(duì)象
// 然后阻塞,等待喚醒
int h = p.hash;
// 如果是計(jì)時(shí)等待交換,則計(jì)算超時(shí)時(shí)間;否則設(shè)置為0。
long end = timed ? System.nanoTime() + ns : 0L;
// 如果CPU核心數(shù)大于1,則使用SPINS數(shù),自旋;否則為1,沒(méi)必要自旋。
int spins = (NCPU > 1) ? SPINS : 1;
// 記錄對(duì)方線程交換來(lái)的數(shù)據(jù)
Object v;
// 如果p.match==null,表示還沒(méi)有線程交換來(lái)數(shù)據(jù)
while ((v = p.match) == null) {
// 如果自旋次數(shù)大于0,計(jì)算hash隨機(jī)數(shù)
if (spins > 0) {
// 生成隨機(jī)數(shù),用于自旋次數(shù)控制
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();
}
// p是ThreadLocal記錄的當(dāng)前線程的Node。
// 如果slot不是p表示slot是別的線程放進(jìn)去的
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);
}
else if (U.compareAndSwapObject(this, SLOT, p, null)) {
// 沒(méi)有被中斷但是超時(shí)了,返回TIMED_OUT,否則返回null
v = timed && ns <= 0L && !t.isInterrupted() ? TIMED_OUT : null;
break;
}
}
// match設(shè)置為null值 CAS
U.putOrderedObject(p, MATCH, null);
p.item = null;
p.hash = h;
// 返回獲取的對(duì)方線程交換來(lái)的數(shù)據(jù)
return v;
}
arenaExchange的實(shí)現(xiàn):
/**
* Exchange function when arenas enabled. See above for explanation.
* 當(dāng)啟用arenas的時(shí)候,使用該方法進(jìn)行線程間的數(shù)據(jù)交換。
* @param item 本線程要交換的非null數(shù)據(jù)。
* @param timed 如果需要計(jì)時(shí)等待,則設(shè)置為true。
* @param ns 表示計(jì)時(shí)等待的最大時(shí)長(zhǎng)。
* @return 對(duì)方線程交換來(lái)的數(shù)據(jù)。如果線程被中斷,或者等待超時(shí),則返回null。
*/
private final Object arenaExchange(Object item, boolean timed, long ns) {
Node[] a = arena;
Node p = participant.get();
// 訪問(wèn)下標(biāo)為i處的slot數(shù)據(jù)
for (int i = p.index;;) { // access slot at i
int b, m, c; long j; // j is raw array offset
Node q = (Node)U.getObjectVolatile(a, j = (i << ASHIFT) + ABASE);
// 如果q不是null,則將數(shù)組的第j個(gè)元素由q設(shè)置為null
if (q != null && U.compareAndSwapObject(a, j, q, null)) {
// 獲取對(duì)方線程交換來(lái)的數(shù)據(jù)
Object v = q.item; // release
// 設(shè)置本方線程交換的數(shù)據(jù)
q.match = item;
Thread w = q.parked;
if (w != null)
// 如果對(duì)方線程非空,則喚醒對(duì)方線程
U.unpark(w);
return v;
}
// 如果自旋次數(shù)沒(méi)達(dá)到邊界,且q為null
else if (i <= (m = (b = bound) & MMASK) && q == null) {
// 提供本方數(shù)據(jù)
p.item = item; // offer
// 將arena的第j個(gè)元素由null設(shè)置為p
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;;) {
// 獲取對(duì)方交換來(lái)的數(shù)據(jù)
Object v = p.match;
// 如果對(duì)方交換來(lái)的數(shù)據(jù)非空
if (v != null) {
// 將p設(shè)置為null,CAS操作
U.putOrderedObject(p, MATCH, null);
// 清空
p.item = null; // clear for next use
p.hash = h;
// 返回交換來(lái)的數(shù)據(jù)
return v;
}
// 產(chǎn)生隨機(jī)數(shù),用于限制自旋次數(shù)
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
}
// 如果arena的第j個(gè)元素不是p
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);
}
// arena的第j個(gè)元素是p并且CAS設(shè)置arena的第j個(gè)元素由p設(shè)置 為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
// 如果線程被中斷,則返回null值
if (Thread.interrupted())
return null;
if (timed && m == 0 && ns <= 0L)
// 如果超時(shí),返回TIMED_OUT。
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;
}
}
}