CountDownLatch
CountDownLatch閉鎖相當於一扇門,在閉鎖到達結束狀態之前,這扇門一直是關閉的,並且沒有任何執行緒能通過,當到達結束狀態時,這扇門會開啟並允許所有的執行緒通過。當閉鎖到達結束狀態後,將不會再改變狀態,門永遠保持開啟狀態
CountDownLatch實現原理
CountDownLatch通過內部類Sync實現方法,sync繼承AQS重寫模板中的方法。sync內部定義:
private static final class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 4982264981922014374L;
Sync(int count) {
setState(count);
}
/**
* 獲取同步狀態
*/
int getCount() {
return getState();
}
/**
* 獲取同步狀態
*/
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
/**
* 釋放同步狀態
*/
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;
}
}
}
複製程式碼
從原始碼中重寫的方法可以得知,CountDownLatch中的sync採用共享模式。CountDownLatch示例:
public class TestHarness {
public static long timeTasks(int nThreads, final Runnable task) throws InterruptedException {
final CountDownLatch startGate = new CountDownLatch(1);
final CountDownLatch endGate = new CountDownLatch(nThreads);
for (int i = 0; i < nThreads; i++) {
Thread t = new Thread() {
@Override
public void run() {
try {
startGate.await();
try {
System.out.println(Thread.currentThread().getName() + "開始執行");
task.run();
} finally {
endGate.countDown();
System.out.println(Thread.currentThread().getName() + "執行結束");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
t.start();
}
long start = System.nanoTime();
startGate.countDown();
endGate.await();
long end = System.nanoTime();
System.out.println("所有執行緒執行完畢,耗時:" + (end-start));
return end - start;
}
public static void main(String[] args) throws InterruptedException {
System.out.println(timeTasks(10, new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "————————work");
}
}));
}
}
複製程式碼
執行結果:
Thread-0開始執行
Thread-3開始執行
Thread-0————————work
Thread-0執行結束
Thread-1開始執行
Thread-2開始執行
Thread-7開始執行
Thread-7————————work
Thread-7執行結束
Thread-9開始執行
Thread-9————————work
Thread-9執行結束
Thread-8開始執行
Thread-8————————work
Thread-8執行結束
Thread-2————————work
Thread-2執行結束
Thread-6開始執行
Thread-1————————work
Thread-6————————work
Thread-6執行結束
Thread-5開始執行
Thread-5————————work
Thread-5執行結束
Thread-3————————work
Thread-3執行結束
Thread-4開始執行
Thread-1執行結束
Thread-4————————work
Thread-4執行結束
所有執行緒執行完畢,耗時:2794976
2794976
複製程式碼
CyclicBarrier
相對於CountDownLatch是一次性物件,一旦進入終止狀態,就不能被重置,CyclicBarrier可以反覆使用。CyclicBarrier類似於閉鎖,與閉鎖的關鍵區別在於,閉鎖用於等待事件,柵欄用於等待其他執行緒,其作用是讓一組執行緒到達一個屏障(也可以叫同步點)時被阻塞,直到最後一個執行緒到達屏障時,屏障才會開門,所有被屏障攔截的執行緒才會繼續執行。
CyclicBarrier實現原理
CyclicBarrier構造方法
public CyclicBarrier(int parties) {
this(parties, null);
}
public CyclicBarrier(int parties, Runnable barrierAction) {
if (parties <= 0) throw new IllegalArgumentException();
this.parties = parties;
this.count = parties;
this.barrierCommand = barrierAction;
}
複製程式碼
引數parties指柵欄攔截的執行緒數量
引數barrierAction指當這些執行緒都到達柵欄時優先會執行的執行緒
await()方法
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();
// 若執行緒中斷,斷開CyclicBarrier
if (Thread.interrupted()) {
breakBarrier();
throw new InterruptedException();
}
int index = --count;
// count為0表明所有執行緒到達柵欄位置
if (index == 0) { // tripped
boolean ranAction = false;
try {
// 若初始化時指定了所有執行緒到達柵欄時的任務,執行它
final Runnable command = barrierCommand;
if (command != null)
command.run();
ranAction = true;
// 喚醒所有等待執行緒,開始新的generation
nextGeneration();
return 0;
} finally {
// 若任務執行異常,斷開CyclicBarrier
if (!ranAction)
breakBarrier();
}
}
// 迴圈所有執行緒到達柵欄或柵欄斷開或執行緒中斷或超時
for (;;) {
try {
// 一直等待
if (!timed)
trip.await();
// 限時等待
else if (nanos > 0L)
nanos = trip.awaitNanos(nanos);
} catch (InterruptedException ie) {
// 若執行緒中斷且柵欄沒有斷開,斷開CyclicBarrier
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;
// 若等待超時,斷開CyclicBarrier
if (timed && nanos <= 0L) {
breakBarrier();
throw new TimeoutException();
}
}
} finally {
// 釋放鎖
lock.unlock();
}
}
複製程式碼
其主要邏輯:若有執行緒未到達柵欄位置,到達柵欄位置的執行緒一直等待狀態,直至發生以下場景:
①. 所有執行緒都到達柵欄位置
②. 有執行緒被中斷
③. 執行緒等待超時
④. 有執行緒呼叫reset()方法,斷開當前柵欄,將柵欄重置為初始狀態
reset方法:
public void reset() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
// 斷開當前柵欄
breakBarrier(); // break the current generation
// 開始新的generation
nextGeneration(); // start a new generation
} finally {
lock.unlock();
}
}
複製程式碼
CyclicBarrier示例
public class CyclicBarrierTest {
private static CyclicBarrier cyclicBarrier;
static class CyclicBarrierThread extends Thread{
public void run() {
System.out.println("運動員:" + Thread.currentThread().getName() + "到場");
try {
cyclicBarrier.await();
} catch (Exception e) {
e.printStackTrace();
}
}
}
public static void main(String[] args){
cyclicBarrier = new CyclicBarrier(5, new Runnable() {
@Override
public void run() {
System.out.println("運動員全部到齊,比賽開始");
}
});
for(int i = 0 ; i < 5 ; i++){
new CyclicBarrierThread().start();
}
}
}
複製程式碼
執行結果:
運動員:Thread-0到場
運動員:Thread-1到場
運動員:Thread-2到場
運動員:Thread-3到場
運動員:Thread-4到場
運動員全部到齊,比賽開始
複製程式碼
CountDownLatch與CyclicBarrier區別
①.CountDownLatch的計數器只能使用一次,而CyclicBarrier的計數器可以使用reset()方法重置。所以CyclicBarrier能處理更為複雜的業務場景。例如,如果計算髮生錯誤,可以重置計數器,並讓執行緒重新執行一次
②.CyclicBarrier還提供其他有用的方法,比如getNumberWaiting方法可以獲得Cyclic-Barrier阻塞的執行緒數量。isBroken()方法用來了解阻塞的執行緒是否被中斷
③.CountDownLatch傾向於一個執行緒等多個執行緒,CyclicBarrier傾向於多個執行緒互相等待