CyclicBarrier – 同步屏障實現分析

斷風雨發表於2019-03-02

CyclicBarrier

CyclicBarrier 是可迴圈使用的屏障,主要功能是讓一組執行緒到達一個屏障時被阻塞,直到最後一個執行緒到達屏障時,屏障才會開啟;所有被屏障攔截的執行緒才會繼續執行。

使用示例

public class CyclicBarrierTest {

    // 執行緒個數
    private int parties = 3;

    private AtomicInteger atomicInteger = new AtomicInteger(parties);

    private CyclicBarrier cyclicBarrier;

    class Protector implements Runnable {

        @Override
        public void run() {
            try {
                System.out.println(Thread.currentThread().getName() + " - 到達屏障前");

                TimeUnit.SECONDS.sleep(2);

                cyclicBarrier.await();

                atomicInteger.decrementAndGet();

                System.out.println(Thread.currentThread().getName() + " - 到達屏障後");

            } catch (InterruptedException e) {
                System.out.println(Thread.currentThread().getName() + " - 等待中斷");
            } catch (BrokenBarrierException e) {
                System.out.println(Thread.currentThread().getName() + " - 屏障被破壞");
            }
        }
    }


    @Before
    public void init() {
        cyclicBarrier = new CyclicBarrier(parties);
    }

    @Test
    public void allAwait() {
        for (int i = 0; i < parties; i++) {
            new Thread(new Protector(), "Thread-" + i).start();
        }

        while (true) {
            if (atomicInteger.get() == 0) {
                // 所有執行緒到達屏障後退出結束
                System.out.println("test over");
                break;
            }
        }
    }

    @Test
    public void oneAwaitInterrupted() throws InterruptedException {
        Thread threadA = new Thread(new Protector(), "Thread-A");
        Thread threadB = new Thread(new Protector(), "Thread-B");

        threadA.start();
        threadB.start();
        // 等待 3 秒,避免是 time sleep 觸發中斷異常
        TimeUnit.SECONDS.sleep(3);

        threadA.interrupt();

        while (true) {
            if (atomicInteger.get() == 0) {
                System.out.println("test over");
                break;
            }
            if (cyclicBarrier.isBroken()) {
                System.out.println("屏障中斷退出");
                break;
            }
        }
    }
}
複製程式碼
Thread-A - 到達屏障前
Thread-B - 到達屏障前
屏障中斷退出
Thread-A - 等待中斷
Thread-B - 屏障被破壞

Thread-0 - 到達屏障前
Thread-1 - 到達屏障前
Thread-2 - 到達屏障前
Thread-2 - 到達屏障後
Thread-0 - 到達屏障後
Thread-1 - 到達屏障後
test over
複製程式碼

從 oneAwaitInterrupted 方法執行結果可以看出,當一個執行緒 A 執行中斷時,另外一個執行緒 B 會丟擲 BrokenBarrierException

構造

// 可以指定攔截執行緒個數
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;
}
複製程式碼

實現

概念
  • barrier : 屏障
  • parties : 為屏障攔截的執行緒數
  • tripped : 跳閘,可以理解為開啟屏障
  • generation.broken : 屏障是否破損,當屏障被開啟或被重置的時候會改變值

簡單的理解就是,當執行緒都到達屏障的時候,會開啟屏障。

await()

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();

        if (Thread.interrupted()) {
        	// 執行緒中斷 則退出屏障
            breakBarrier();
            throw new InterruptedException();
        }

        // 到達屏障的計數減一
        int index = --count;      
        if (index == 0) {  // tripped
        	// index == 0, 說明指定 count 的執行緒均到達屏障
        	// 此時可以開啟屏障
            boolean ranAction = false;
            try {
                final Runnable command = barrierCommand;
                if (command != null)
                	// 若指定了 barrierCommand 則執行
                    command.run();
                ranAction = true;
                // 喚醒阻塞在屏障的執行緒並重置 generation
                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)
            	// g != generation 說明所有執行緒均到達屏障處 可直接返回
            	// 因為所有執行緒到達屏障處的時候,會重置 generation
            	// 參考 nextGeneration
                return index;

            if (timed && nanos <= 0L) {
            	// 說明指定時間內,還有執行緒未到達屏障處,也就是等待超時
            	// 退出屏障
                breakBarrier();
                throw new TimeoutException();
            }
        }
    } finally {
        lock.unlock();
    }
}
複製程式碼
private void nextGeneration() {
    // signal completion of last generation
    // 喚醒阻塞在等待佇列的執行緒
    trip.signalAll();
    // set up next generation
    // 重置 count
    count = parties;
    // 重置 generation
    generation = new Generation();
}
複製程式碼
private void breakBarrier() {
	// broken 設定為 true
    generation.broken = true;
    // 重置 count
    count = parties;
    // 喚醒等待佇列的執行緒
    trip.signalAll();
}
複製程式碼

如下圖為 CyclicBarrier 實現效果圖:

CyclicBarrier – 同步屏障實現分析
isBroken()

返回屏障是否被破壞,也是是否被中斷

public boolean isBroken() {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        return generation.broken;
    } finally {
        lock.unlock();
    }
}
複製程式碼
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();
    }
}
複製程式碼
getNumberWaiting

獲取阻塞在屏障處的執行緒數

public int getNumberWaiting() {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        // 攔截執行緒數 - 未到達屏障數
        return parties - count;
    } finally {
        lock.unlock();
    }
}
複製程式碼

小結

CyclicBarrier 和 CountDownLatch 功能類似,不同之處在於 CyclicBarrier 支援重複利用,而 CountDownLatch 計數只能使用一次。

相關文章