多執行緒與併發----CycliBarrier、CountDownLatch 和 Exchanger同步

pengzhisen123發表於2018-05-13

一、CycliBarrier

        表示大家彼此等待,大家集合號後才開始出發,分散活動後又在指定地點集合碰面,這就好比整個公司的人員利用週末時間集體郊遊一樣,先各自從家出發到公司集合後,在他同時出發到公園遊玩,在指定地點集合後再同時就餐。

        功能:        

    一個同步輔助類,它允許一組執行緒互相等待,直到到達某個公共屏障點 (common barrier point)。在涉及一組固定大小的執行緒的程式中,這些執行緒必須不時地互相等待,此時 CyclicBarrier 很有用。因為該 barrier 在釋放等待執行緒後可以重用,所以稱它為迴圈  barrier

        CyclicBarrier 支援一個可選的 Runnable 命令,在一組執行緒中的最後一個執行緒到達之後(但在釋放所有執行緒之前),該命令只在每個屏障點執行一次。若在繼續所有參與執行緒之前更新共享狀態,此屏障操作 很有用。       

構造方法摘要

CyclicBarrier(int parties)           建立一個新的 CyclicBarrier,它將在給定數量的參與者(執行緒)處於等待狀態時啟動,但它不會在啟動 barrier 時執行預定義的操作。

CyclicBarrier(int parties, Runnable barrierAction)           建立一個新的 CyclicBarrier,它將在給定數量的參與者(執行緒)處於等待狀態時啟動,並在啟動 barrier 時執行給定的屏障操作,該操作由最後一個進入 barrier 的執行緒執行。

方法摘要

 int

await()     在所有參與者都已經在此 barrier 上呼叫 await 方法之前,將一直等待。

 int

await(long timeout, TimeUnit unit)           在所有參與者都已經在此屏障上呼叫 await 方法之前將一直等待,或者超出了指定的等待時間。

 int

getNumberWaiting()           返回當前在屏障處等待的參與者數目。

 int

getParties()           返回要求啟動此 barrier 的參與者數目

 boolean

isBroken()           查詢此屏障是否處於損壞狀態。

 void

reset()           將屏障重置為其初始狀態。

示例程式碼:

import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class CylicBarrierTest {

	public static void main(String[] args) {
		ExecutorService service = Executors.newCachedThreadPool();
		final CyclicBarrier cb = new CyclicBarrier(3);  //約定3個人
		for (int i=0; i<3; i++)//產生3個人
		{	
			//每個人的任務
			Runnable runnable = new Runnable()
			{
				public void run()
				{
					try {
						//開始出發到目的地
						Thread.sleep((long)Math.random()*10000);
						System.out.println("執行緒 "+Thread.currentThread().getName()+
								" 即將到達集合地點1,當前已有 "+(cb.getNumberWaiting()+1)+
								" 個到達,"+(cb.getNumberWaiting()==2?" 都到齊了,繼續走啊 ":" 正在等候"));
						cb.await();//到了其他人沒來就等人到齊了再繼續進行
						
						Thread.sleep((long)Math.random()*10000);
						System.out.println("執行緒 "+Thread.currentThread().getName()+
								" 即將到達集合地點2,當前已有 "+(cb.getNumberWaiting()+1)+
								" 個到達,"+(cb.getNumberWaiting()==2?" 都到齊了,繼續走啊 ":" 正在等候 "));
						
						cb.await();//到了其他人沒來就等人到齊了再繼續進行
						
						Thread.sleep((long)Math.random()*10000);
						System.out.println("執行緒 "+Thread.currentThread().getName()+
								" 即將到達集合地點3,當前已有 "+(cb.getNumberWaiting()+1)+
								" 個到達,"+(cb.getNumberWaiting()==2?" 都到齊了,繼續走啊 ":" 正在等候"));

						//cb.await();
					} catch (Exception e) {
						e.printStackTrace();
					}
				}
			};
			service.execute(runnable);
		}
		//service.shutdown();
	}
}

執行結果為:

執行緒 pool-1-thread-1 即將到達集合地點1,當前已有 1 個到達, 正在等候

執行緒 pool-1-thread-2 即將到達集合地點1,當前已有 2 個到達, 正在等候

執行緒 pool-1-thread-3 即將到達集合地點1,當前已有 3 個到達, 都到齊了,繼續走啊 

執行緒 pool-1-thread-3 即將到達集合地點2,當前已有 1 個到達, 正在等候 

執行緒 pool-1-thread-1 即將到達集合地點2,當前已有 2 個到達, 正在等候 

執行緒 pool-1-thread-2 即將到達集合地點2,當前已有 3 個到達, 都到齊了,繼續走啊 

二、CountDownLatch

       1、功能:

    好像倒數計時計數器,呼叫CountDownLatch物件的countDown方法就將計數器減 1,當到達 0 時,所有等待著就開始執行。

  2、原理

                java.util.concurrent.CountDownLatch

一個同步輔助類,在完成一組正在其他執行緒中執行的操作之前,它允許一個或多個執行緒一直等待。 用給定的計數 初始化 CountDownLatch。由於呼叫了 countDown() 方法,所以在當前計數到達零之前,await 方法會一直受阻塞。之後,會釋放所有等待的執行緒,await 的所有後續呼叫都將立即返回。這種現象只出現一次——計數無法被重置。如果需要重置計數,請考慮使用 CyclicBarrier

CountDownLatch 是一個通用同步工具,它有很多用途。將計數 1 初始化的 CountDownLatch 用作一個簡單的開/關鎖存器,或入口:在通過呼叫 countDown() 的執行緒開啟入口前,所有呼叫 await 的執行緒都一直在入口處等待。用 N 初始化的 CountDownLatch 可以使一個執行緒在 N 個執行緒完成某項操作之前一直等待,或者使其在某項操作完成 N 次之前一直等待。

CountDownLatch 的一個有用特性是,它不要求呼叫 countDown 方法的執行緒等到計數到達零時才繼續,而在所有執行緒都能通過之前,它只是阻止任何執行緒繼續通過一個 await

構造方法摘要

CountDownLatch(int count)           構造一個用給定計數初始化的 CountDownLatch

方法摘要

 void

await()           使當前執行緒在鎖存器倒計數至零之前一直等待,除非執行緒被中斷

 boolean

await(long timeout, TimeUnit unit)           使當前執行緒在鎖存器倒計數至零之前一直等待,除非執行緒被中斷或超出了指定的等待時間。

 void

countDown()           遞減鎖存器的計數,如果計數到達零,則釋放所有等待的執行緒。

 long

getCount()           返回當前計數。

 String

toString()           返回標識此鎖存器及其狀態的字串。

示例程式碼:多個運動員等待裁判命令: 裁判等所有運動員到齊後釋出結果

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class CountdownLatchTest {

	public static void main(String[] args) {
		
		ExecutorService service = Executors.newCachedThreadPool();
		
		//裁判釋出命令的計數器,計數器為0,運動員就跑
		final CountDownLatch cdOrder = new CountDownLatch(1);	
		
		//運動員跑到終點的計數器,為0裁判宣佈結果
		final CountDownLatch cdAnswer = new CountDownLatch(3);
		
		//產生3個運動員
		for (int i=0; i<3; i++)
		{	//運動員的任務
			Runnable runnable = new Runnable(){
			public void run()
			{
				try {
					System.out.println("執行緒"+Thread.currentThread().getName()+"正準備接受命令");
					
					//等待發布命令
					cdOrder.await();	//計數器為0繼續向下執行
					System.out.println("執行緒"+Thread.currentThread().getName()+"已接受命令");
					
					Thread.sleep((long)(Math.random()*10000));//開始跑步
					System.out.println("執行緒"+Thread.currentThread().getName()+"回應命令處理結果");
					cdAnswer.countDown();//跑到終點了,計數器減1
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		};
			service.execute(runnable);//運動員開始任務
		}
		try {
			Thread.sleep((long)(Math.random()*10000));//開始跑步
			System.out.println("執行緒"+Thread.currentThread().getName()+"即將釋出命令");
			cdOrder.countDown();//跑到終點了,計數器減1
			System.out.println("執行緒"+Thread.currentThread().getName()+"已傳送命令,正在等待結果");
			cdAnswer.await();
			System.out.println("執行緒"+Thread.currentThread().getName()+"已收到所有響應結果");
			
		} catch (Exception e) {
			e.printStackTrace();
		}
		service.shutdown();
	}
}

執行結果為:

執行緒pool-1-thread-2正準備接受命令

執行緒pool-1-thread-3正準備接受命令

執行緒pool-1-thread-1正準備接受命令

執行緒main即將釋出命令

執行緒main已傳送命令,正在等待結果

執行緒pool-1-thread-1已接受命令

執行緒pool-1-thread-2已接受命令

執行緒pool-1-thread-3已接受命令

執行緒pool-1-thread-3回應命令處理結果

執行緒pool-1-thread-1回應命令處理結果

執行緒pool-1-thread-2回應命令處理結果

執行緒main已收到所有響應結果


三、Exchanger

        1、功能

            用於實現兩個人之間的資料交換,每個人在完成一定的事務後想與對方交換資料,第一個先拿出資料的人會一直等待第二個人,直到第二個人拿著資料到來時,才能彼此交換資料。

        2、原理

          java.util.concurrent.Exchanger<V> V - 可以交換的物件型別

 可以在對中對元素進行配對和交換的執行緒的同步點。每個執行緒將條目上的某個方法呈現給 exchange 方法,與夥伴執行緒進行匹配,並且在返回時接收其夥伴的物件。Exchanger 可能被視為 SynchronousQueue 的雙向形式。Exchanger 可能在應用程式(比如遺傳演算法和管道設計)中很有用。 

    

構造方法摘要

Exchanger()           建立一個新的 Exchanger

方法摘要

 V

exchange(V x)           等待另一個執行緒到達此交換點(除非當前執行緒被中斷),然後將給定的物件傳送給該執行緒,並接收該執行緒的物件。

 V

exchange(V x, long timeout, TimeUnit unit)           等待另一個執行緒到達此交換點(除非當前執行緒被中斷,或者超出了指定的等待時間),然後將給定的物件傳送給該執行緒,同時接收該執行緒的物件。

        3、舉例

    毒品交易  雙方並不是同時到達,有先有後,只有都到達了,瞬間交換資料,各自飛

程式碼演示:

import java.util.concurrent.Exchanger;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ExchangerTest {

	public static void main(String[] args) {
		ExecutorService service = Executors.newCachedThreadPool();
		final Exchanger exchanger = new Exchanger();
		//毒販:
		service.execute(new Runnable()
		{	
			//毒販做的事
			public void run()
			{
				try {
					String data1 ="abc";
					System.out.println("執行緒 "+Thread.currentThread().getName()+
							" 正在把資料 "+data1+" 換出去");
					Thread.sleep((long)(Math.random()*10000));  //換的過程
					//毒販到位了,拿著毒品等待毒人接頭,接頭後就能換到錢了
					String data2 = (String)exchanger.exchange(data1);
					System.out.println("執行緒 "+Thread.currentThread().getName()+"換回的資料為 "+data2);
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		});
		//毒人:
		service.execute(new Runnable()
		{	
			//吸毒人做的事
			public void run()
			{
				try {
					String data1 ="123";
					System.out.println("執行緒 "+Thread.currentThread().getName()+
							" 正在把資料 "+data1+" 換出去");
					Thread.sleep((long)(Math.random()*10000));   //換的過程
					//吸毒人到位了,拿著錢等待毒販接頭,接頭後就能換到毒品了
					String data2 = (String)exchanger.exchange(data1);
					System.out.println("執行緒 "+Thread.currentThread().getName()+"換回的資料為 "+data2);
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		});
	}
}

執行結果為:

執行緒 pool-1-thread-1 正在把資料 abc 換出去

        執行緒 pool-1-thread-2 正在把資料 123 換出去

        執行緒 pool-1-thread-1換回的資料為 123

        執行緒 pool-1-thread-2換回的資料為 abc



相關文章