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進行復用.....
-
又來了一撥人,看看願不願意一起打:
-
王二開始從宿舍出發
-
洪光開始從宿舍出發
-
雷兵開始從宿舍出發
-
趙三開始從宿舍出發
-
王二從樓底下出發
-
洪光從樓底下出發
-
雷兵從樓底下出發
-
趙三從樓底下出發
-
洪光到達操場
-
王二到達操場
-
雷兵到達操場
-
趙三到達操場
-
四個人一起到達球場,表示願意一起打球,現在八個人開始打球
3.CountDownLatch和CyclicBarrier的比較
1.CountDownLatch是線程組之間的等待,即一個(或多個)線程等待N個線程完成某件事情之后再執行;而CyclicBarrier則是線程組內的等待,即每個線程相互等待,即N個線程都被攔截之后,然后依次執行。
2.CountDownLatch是減計數方式,而CyclicBarrier是加計數方式。
3.CountDownLatch計數為0無法重置,而CyclicBarrier計數達到初始值,則可以重置。
4.CountDownLatch不可以復用,而CyclicBarrier可以復用。