多线程与并发----CycliBarrier、CountDownLatch 和 Exchanger同步

一、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



猜你喜欢

转载自blog.csdn.net/pengzhisen123/article/details/80300491