1. 程式人生 > >CyclicBarrier的工作原理及其例項

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可以複用。