CyclicBarrier的工作原理及其例項
CyclicBarrier是多執行緒中一個重要的類,主要用於執行緒組內部之間的執行緒的相互等待問題。
1.CyclicBarrier的工作原理
CyclicBarrier大致是可迴圈利用的屏障,顧名思義,這個名字也將這個類的特點給明確地表示出來了。首先,便是可重複利用,說明該類建立的物件可以複用;其次,屏障則體現了該類的原理:每個執行緒執行時,都會碰到一個屏障,直到所有執行緒執行結束,然後屏障便會開啟,使所有執行緒繼續往下執行。
這裡介紹CyclicBarrier的兩個建構函式:CyclicBarrier(int parties)和CyclicBarrier(int parties, Runnable barrierAction) :前者只需要宣告需要攔截的執行緒數即可,而後者還需要定義一個等待所有執行緒到達屏障優先執行的Runnable物件。
實現原理:在CyclicBarrier的內部定義了一個Lock物件,每當一個執行緒呼叫await方法時,將攔截的執行緒數減1,然後判斷剩餘攔截數是否為初始值parties,如果不是,進入Lock物件的條件佇列等待。如果是,執行barrierAction物件的Runnable方法,然後將鎖的條件佇列中的所有執行緒放入鎖等待佇列中,這些執行緒會依次的獲取鎖、釋放鎖。
舉例說明:如果一個寢室四個人約好了去球場打球,由於四個人準備工作不同,所以約好在樓下集合,並且四個人集合好之後一起出發去球場。
package concurrent; import java.util.concurrent.CyclicBarrier; import java.util.concurrent.*; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.*; public class CyclicBarrierDemo { private static final ThreadPoolExecutor threadPool=new ThreadPoolExecutor(4,10,60,TimeUnit.SECONDS,new LinkedBlockingQueue<Runnable>()); //當攔截執行緒數達到4時,便優先執行barrierAction,然後再執行被攔截的執行緒。 private static final CyclicBarrier cb=new CyclicBarrier(4,new Runnable() { public void run() { System.out.println("寢室四兄弟一起出發去球場"); } }); private static class GoThread extends Thread{ private final String name; public GoThread(String name) { this.name=name; } public void run() { System.out.println(name+"開始從宿舍出發"); try { Thread.sleep(1000); cb.await();//攔截執行緒 System.out.println(name+"從樓底下出發"); Thread.sleep(1000); System.out.println(name+"到達操場"); } catch(InterruptedException e) { e.printStackTrace(); } catch(BrokenBarrierException e) { e.printStackTrace(); } } } public static void main(String[] args) { // TODO Auto-generated method stub String[] str= {"李明","王強","劉凱","趙傑"}; for(int i=0;i<4;i++) { threadPool.execute(new GoThread(str[i])); } try { Thread.sleep(4000); System.out.println("四個人一起到達球場,現在開始打球"); } catch(InterruptedException e) { e.printStackTrace(); } } }
執行程式,得到如下結果:
李明開始從宿舍出發
趙傑開始從宿舍出發
王強開始從宿舍出發
劉凱開始從宿舍出發
寢室四兄弟一起出發去球場
趙傑從樓底下出發
李明從樓底下出發
劉凱從樓底下出發
王強從樓底下出發
趙傑到達操場
王強到達操場
李明到達操場
劉凱到達操場
四個人一起到達球場,現在開始打球
以上便是CyclicBarrier使用例項,通過await()方法對執行緒的攔截,攔截數加1,當攔截數為初始的parties,首先執行了barrierAction,然後對攔截的執行緒佇列依次進行獲取鎖釋放鎖。接下來,在這個例子上講解CyclicBarrier物件的複用特性。
package concurrent; import java.util.concurrent.CyclicBarrier; import java.util.concurrent.*; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.*; public class CyclicBarrierDemo { private static final ThreadPoolExecutor threadPool=new ThreadPoolExecutor(4,10,60,TimeUnit.SECONDS,new LinkedBlockingQueue<Runnable>()); //當攔截執行緒數達到4時,便優先執行barrierAction,然後再執行被攔截的執行緒。 private static final CyclicBarrier cb=new CyclicBarrier(4,new Runnable() { public void run() { System.out.println("寢室四兄弟一起出發去球場"); } }); private static class GoThread extends Thread{ private final String name; public GoThread(String name) { this.name=name; } public void run() { System.out.println(name+"開始從宿舍出發"); try { Thread.sleep(1000); cb.await();//攔截執行緒 System.out.println(name+"從樓底下出發"); Thread.sleep(1000); System.out.println(name+"到達操場"); } catch(InterruptedException e) { e.printStackTrace(); } catch(BrokenBarrierException e) { e.printStackTrace(); } } } public static void main(String[] args) { // TODO Auto-generated method stub String[] str= {"李明","王強","劉凱","趙傑"}; String[] str1= {"王二","洪光","雷兵","趙三"}; for(int i=0;i<4;i++) { threadPool.execute(new GoThread(str[i])); } try { Thread.sleep(4000); System.out.println("四個人一起到達球場,現在開始打球"); System.out.println("現在對CyclicBarrier進行復用....."); System.out.println("又來了一撥人,看看願不願意一起打:"); } catch(InterruptedException e) { e.printStackTrace(); } //進行復用: for(int i=0;i<4;i++) { threadPool.execute(new GoThread(str1[i])); } try { Thread.sleep(4000); System.out.println("四個人一起到達球場,表示願意一起打球,現在八個人開始打球"); //System.out.println("現在對CyclicBarrier進行復用"); } catch(InterruptedException e) { e.printStackTrace(); } } }
執行如下程式,得到:
王強開始從宿舍出發
趙傑開始從宿舍出發
李明開始從宿舍出發
劉凱開始從宿舍出發
寢室四兄弟一起出發去球場
王強從樓底下出發
李明從樓底下出發
劉凱從樓底下出發
趙傑從樓底下出發
王強到達操場
李明到達操場
趙傑到達操場
劉凱到達操場
四個人一起到達球場,現在開始打球
現在對CyclicBarrier進行復用.....
又來了一撥人,看看願不願意一起打:
王二開始從宿舍出發
雷兵開始從宿舍出發
洪光開始從宿舍出發
趙三開始從宿舍出發
寢室四兄弟一起出發去球場
洪光從樓底下出發
王二從樓底下出發
雷兵從樓底下出發
趙三從樓底下出發
雷兵到達操場
趙三到達操場
洪光到達操場
王二到達操場
四個人一起到達球場,表示願意一起打球,現在八個人開始打球
由上面例項可瞭解CyclicBarrier的工作原理以及複用的特性。
2.通過CountDownLatch實現CyclicBarrier
CountDownLatch通過將await()方法和countDown()方法在不同執行緒組分別呼叫,從而實現執行緒組間的執行緒等待,即一個執行緒組等待另一個執行緒組執行結束再執行。而CyclicBarrier類則是通過呼叫await()方法實現執行緒組內的執行緒等待,即達到需要攔截的執行緒數,被攔截的執行緒才會依次獲取鎖,釋放鎖。那麼將CountDownLatch的用法進行轉換,即在同一個執行緒組內呼叫await()方法和countDown()方法,則可實現CyclicBarrier類的功能。但是注意的是必須先呼叫countDown()方法,才能呼叫await()方法,因為一旦呼叫await()方法,該執行緒後面的內容便不再執行,那麼count值無法改變。具體程式碼如下:
package concurrent;
import java.util.Vector;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class CyclicBarrierWithCount {
private final static CountDownLatch cdl=new CountDownLatch(3);
private final static ThreadPoolExecutor threadPool= new ThreadPoolExecutor(10, 15, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());//使用執行緒池
private static class GoThread extends Thread{
private final String name;
public GoThread(String name)
{
this.name=name;
}
public void run()
{
System.out.println(name+"開始從宿舍出發");
cdl.countDown();
try
{
Thread.sleep(1000);
cdl.await();//攔截執行緒
System.out.println(name+"從樓底下出發");
Thread.sleep(1000);
System.out.println(name+"到達操場");
}
catch(InterruptedException e)
{
e.printStackTrace();
}
}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
String[] str= {"李明","王強","劉凱","趙傑"};
String[] str1= {"王二","洪光","雷兵","趙三"};
for(int i=0;i<4;i++)
{
threadPool.execute(new GoThread(str[i]));
}
try
{
Thread.sleep(4000);
System.out.println("四個人一起到達球場,現在開始打球");
System.out.println("現在對CyclicBarrier進行復用.....");
System.out.println("又來了一撥人,看看願不願意一起打:");
}
catch(InterruptedException e)
{
e.printStackTrace();
}
for(int i=0;i<4;i++)
{
threadPool.execute(new GoThread(str1[i]));
}
try
{
Thread.sleep(4000);
System.out.println("四個人一起到達球場,表示願意一起打球,現在八個人開始打球");
//System.out.println("現在對CyclicBarrier進行復用");
}
catch(InterruptedException e)
{
e.printStackTrace();
}
}
}
執行上述程式碼,結果如下:
李明開始從宿舍出發
趙傑開始從宿舍出發
王強開始從宿舍出發
劉凱開始從宿舍出發
王強從樓底下出發
劉凱從樓底下出發
李明從樓底下出發
趙傑從樓底下出發
李明到達操場
趙傑到達操場
王強到達操場
劉凱到達操場
四個人一起到達球場,現在開始打球
現在對CyclicBarrier進行復用.....
又來了一撥人,看看願不願意一起打:
王二開始從宿舍出發
洪光開始從宿舍出發
雷兵開始從宿舍出發
趙三開始從宿舍出發
王二從樓底下出發
洪光從樓底下出發
雷兵從樓底下出發
趙三從樓底下出發
洪光到達操場
王二到達操場
雷兵到達操場
趙三到達操場
四個人一起到達球場,表示願意一起打球,現在八個人開始打球
由上面可知,CountDownLatch一定情況下可以實現CyclicBarrier類的功能。3.CountDownLatch和CyclicBarrier的比較
1.CountDownLatch是執行緒組之間的等待,即一個(或多個)執行緒等待N個執行緒完成某件事情之後再執行;而CyclicBarrier則是執行緒組內的等待,即每個執行緒相互等待,即N個執行緒都被攔截之後,然後依次執行。
2.CountDownLatch是減計數方式,而CyclicBarrier是加計數方式。
3.CountDownLatch計數為0無法重置,而CyclicBarrier計數達到初始值,則可以重置。
4.CountDownLatch不可以複用,而CyclicBarrier可以複用。