併發柵欄CyclicBarrier---簡單問2

濤姐濤哥發表於2019-08-01

併發柵欄CyclicBarrier---簡單問

 

背景:前幾天在網上看到關於Java併發包java.concurrent中一個連環炮的面試題,整理下以備不時之需。

CyclicBarrier簡介:

柵欄類似於閉鎖,它能夠阻塞一組執行緒直到某個事件發生;它與閉鎖(CountDownLatch)的區分關鍵在於,閉鎖是所有執行緒等待一個外部事件的發生;而柵欄則是所有執行緒相互等待,直到所有執行緒都到達某一點時才開啟柵欄,然後執行緒可以繼續執行。

問題:

如果想實現所有的執行緒一起等待某個事件的發生,當某個事件發生時,所有執行緒一起開始往下執行的話,有什麼好的辦法嗎?

回答:

可以使用柵欄,Java併發包中的CyclicBarrier。

又問:

你知道CyclicBarrier的實現原理嗎?

回答:

CyclicBarrier.await方法呼叫CyclicBarrier.dowait方法,每次呼叫await方法都會使計數器-1,當減少到0時就會喚醒所有的執行緒。(計數器count就是執行緒總數,CyclicBarrier cyclicBarrier = new CyclicBarrier(100);)最核心的部分就是 int index = --count; 和 nextGeneration();方法。

1 public int await() throws InterruptedException, BrokenBarrierException {
2 try {
3 return dowait(false, 0L);
4 } catch (TimeoutException toe) {
5 throw new Error(toe); // cannot happen
6 }
7 }
1 public int await(long timeout, TimeUnit unit)
2 throws InterruptedException,
3 BrokenBarrierException,
4 TimeoutException {
5 return dowait(true, unit.toNanos(timeout));
6 }
 1 private int dowait(boolean timed, long nanos)
 2 throws InterruptedException, BrokenBarrierException,
 3 TimeoutException {
 4 final ReentrantLock lock = this.lock;
 5 lock.lock();
 6 try {
 7 final Generation g = generation;
 8 
 9 if (g.broken)
10 throw new BrokenBarrierException();
11 
12 if (Thread.interrupted()) {
13 breakBarrier();
14 throw new InterruptedException();
15 }
16 
17 int index = --count; // 最核心的部分就是此處1
18 if (index == 0) { // tripped
19 boolean ranAction = false;
20 try {
21 final Runnable command = barrierCommand;
22 if (command != null)
23 command.run();
24 ranAction = true;
25 nextGeneration(); // 最核心的部分就是此處2
26 return 0;
27 } finally {
28 if (!ranAction)
29 breakBarrier();
30 }
31 }
32 
33 // loop until tripped, broken, interrupted, or timed out
34 for (;;) {
35 try {
36 if (!timed)
37 trip.await();
38 else if (nanos > 0L)
39 nanos = trip.awaitNanos(nanos);
40 } catch (InterruptedException ie) {
41 if (g == generation && ! g.broken) {
42 breakBarrier();
43 throw ie;
44 } else {
45 // We're about to finish waiting even if we had not
46 // been interrupted, so this interrupt is deemed to
47 // "belong" to subsequent execution.
48 Thread.currentThread().interrupt();
49 }
50 }
51 
52 if (g.broken)
53 throw new BrokenBarrierException();
54 
55 if (g != generation)
56 return index;
57 
58 if (timed && nanos <= 0L) {
59 breakBarrier();
60 throw new TimeoutException();
61 }
62 }
63 } finally {
64 lock.unlock();
65 }
66 }
1 private void nextGeneration() {
2 // signal completion of last generation
3 trip.signalAll();
4 // set up next generation
5 count = parties;
6 generation = new Generation();
7 }

又問:

除此之外,您還知道其它的實現方式嗎?

回答:

方案1:讀寫鎖,剛開始主執行緒獲取寫鎖,然後所有子執行緒獲取讀鎖,然後等事件發生時主執行緒釋放寫鎖;

方案2:CountDownLatch閉鎖,CountDownLatch初始值設為1,所有子執行緒呼叫await方法等待,等事件發生時呼叫countDown方法計數減為0;

方案3:Semaphore,Semaphore初始值設為N,剛開始主執行緒先呼叫acquire(N)申請N個訊號量,其他執行緒呼叫acquire()阻塞等待,等事件發生時同時主執行緒釋放N個訊號量。

CountDownLatch閉鎖實現模擬如下:

 1 import java.util.concurrent.CountDownLatch;
 2 
 3 public class CountDownLatchDemo {
 4 
 5 /** 
 6 * 模擬老爸去飯店 
 7 */ 
 8 public static void fatherToRes() 
 9 { 
10 System.out.println("老爸步行去飯店需要3小時。"); 
11 } 
12 
13 /** 
14 * 模擬老媽去飯店 
15 */ 
16 public static void motherToRes() 
17 { 
18 System.out.println("老媽擠公交去飯店需要2小時。"); 
19 } 
20 
21 /** 
22 * 模擬我去飯店 
23 */ 
24 public static void meToRes() 
25 { 
26 System.out.println("我乘地鐵去飯店需要1小時。"); 
27 } 
28 
29 /** 
30 * 模擬一家人到齊了 
31 */ 
32 public static void togetherToEat() 
33 { 
34 System.out.println("一家人到齊了,開始吃飯"); 
35 } 
36 
37 
38 private static CountDownLatch latch = new CountDownLatch(3); 
39 
40 public static void main(String[] args) throws InterruptedException 
41 { 
42 
43 new Thread() 
44 { 
45 public void run() 
46 { 
47 fatherToRes(); 
48 latch.countDown(); 
49 }; 
50 }.start(); 
51 new Thread() 
52 { 
53 public void run() 
54 { 
55 motherToRes(); 
56 latch.countDown(); 
57 }; 
58 }.start(); 
59 new Thread() 
60 { 
61 public void run() 
62 { 
63 meToRes(); 
64 latch.countDown(); 
65 }; 
66 }.start(); 
67 
68 latch.await(); 
69 togetherToEat(); 
70 } 
71 }

又問:

您覺得這些方式裡哪個方式更好呢?

回答:

CountDownLatch閉鎖是等待一組執行緒執行完畢後才能繼續執行;

CyclicBarrier柵欄是能讓一組執行緒達到一個同步點時被阻塞,直到最後一個執行緒達到,阻塞才會消失,其是可以迴圈使用的;

Semaphore訊號量是隻允許一定數量的執行緒同時執行,一般用來限制訪問資源的執行緒數量。

又問:

如果你這個時候依然可以說出來你自己更好的實現方式,那麼面試官肯定還會揪著這個繼續問你。

 

相關文章