Java多线程编程---Java5同步工具

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/zengxiantao1994/article/details/79967868

Semaphore同步工具

        Semaphore可以维护当前访问自身的线程个数,并且提供了同步机制。

        semaphore实现的功能类似于厕所里有5个坑,有10个人要上厕所,同时就只能有5个人占用,当5个人中的任何一个让开后,其中在等待的另外5个人中又有一个可以占用了。

java.util.concurrent.Semaphore

        一个计数信号量。从概念上讲,信号量维护了一个许可集。如有必要,在许可可用前会阻塞每一个 acquire(),然后再获取该许可。每个 release() 添加一个许可,从而可能释放一个正在阻塞的获取者。但是,不使用实际的许可对象,Semaphore 只对可用许可的号码进行计数,并采取相应的行动。

        Semaphore 通常用于限制可以访问某些资源(物理或逻辑的)的线程数目。例如,下面的类使用信号量控制对内容池的访问:

class Pool {
	private static final int MAX_AVAILABLE = 100;
	private final Semaphore available = new Semaphore(MAX_AVAILABLE, true);

	public Object getItem() throws InterruptedException {
		available.acquire();
		return getNextAvailableItem();
	}

	public void putItem(Object x) {
		if (markAsUnused(x))
			available.release();
	}

	// Not a particularly efficient data structure; just for demo
	protected Object[] items = ... whatever kinds of items being managed
	protected boolean[] used = new boolean[MAX_AVAILABLE];

	protected synchronized Object getNextAvailableItem() {
		for (int i = 0; i < MAX_AVAILABLE; ++i) {
			if (!used[i]) {
				used[i] = true;
				return items[i];
			}
		}
		return null; // not reached
	}

	protected synchronized boolean markAsUnused(Object item) {
		for (int i = 0; i < MAX_AVAILABLE; ++i) {
			if (item == items[i]) {
				if (used[i]) {
					used[i] = false;
					return true;
				} else
					return false;
			}
		}
		return false;
	}
}

        获得一项前,每个线程必须从信号量获取许可,从而保证可以使用该项。该线程结束后,将项返回到池中并将许可返回到该信号量,从而允许其他线程获取该项。注意,调用 acquire() 时无法保持同步锁,因为这会阻止将项返回到池中。信号量封装所需的同步,以限制对池的访问,这同维持该池本身一致性所需的同步是分开的。

构造方法摘要

        Semaphore(int permits):创建具有给定的许可数和非公平的公平设置的 Semaphore。

        Semaphore(int permits, boolean fair):创建具有给定的许可数和给定的公平设置的 Semaphore。

常用方法摘要

        1void acquire():从此信号量获取一个许可,在提供一个许可前一直将线程阻塞,否则线程被中断。

        2、void acquire(int permits):从此信号量获取给定数目的许可,在提供这些许可前一直将线程阻塞,或者线程已被中断。

        3、boolean tryAcquire():仅在调用时此信号量存在一个可用许可,才从信号量获取许可。

        4、boolean tryAcquire(intpermits):仅在调用时此信号量中有给定数目的许可时,才从此信号量中获取这些许可。

        5int availablePermits():返回此信号量中当前可用的许可数。

        6、int drainPermits():获取并返回立即可用的所有许可。

        7、boolean isFair():如果此信号量的公平设置为 true,则返回 true。

        8、protected  void    reducePermits(intreduction):根据指定的缩减量减小可用许可的数目。

        9void    release():释放一个许可,将其返回给信号量。

        10、void release(int permits):释放给定数目的许可,将其返回到信号量。

        11、String toString():返回标识此信号量的字符串,以及信号量的状态。

程序实例

        示例:3个坑 10个人厕所,有多少人都能装,线程数动态变化,来一个人产生一个线程:

        ExecutorService service =Exccutors.newCachedThreadPool();

        final Semaphore sp = new Semaphore(3);厕所中坑的个数  指定只有3个

        3个坑,来了5个人,有2个人要等,其中有一个办完事走了,等待的2个哪个先上呢?默认的构造方法不管,谁抢到了谁上。用new Semaphore(3, true)就可以保证先来的先上。将坑的个数设置为1就可以达到互斥效果,每次只能有一个线程运行。

public class SemaphoreTest {

	public static void main(String[] args) {
		// 创建一个线程池
		ExecutorService service = Executors.newCachedThreadPool();

		// 同时并发的线程数
		final Semaphore sp = new Semaphore(3);

		// 新建10个线程工作
		for (int i = 0; i < 10; i++) {

			Runnable runnable = new Runnable() {

				@Override
				public void run() {

					try {
						// 尝试获取运行许可
						sp.acquire();

					} catch (InterruptedException e) {
						e.printStackTrace();
					}

					System.out.println("线程  " + Thread.currentThread().getName() + " 进入,当前已有  "
							+ (3 - sp.availablePermits()) + " 个并发");
					
					try {
						Thread.sleep((long)(Math.random()*10000));
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					
					System.out.println("线程  " + Thread.currentThread().getName() + " 即将离开");
					sp.release();

					// 下面代码有时候执行不准确,因为其没有和上面的代码合成原子单元
					System.out.println("线程  " + Thread.currentThread().getName() + " 已离开,当前已有  "
							+ (3 - sp.availablePermits()) + " 个并发");
				}
			};

			// 线程提交运行
			service.execute(runnable);
		}
	}
}

        传统互斥只能内部释放锁this.unlock(),进去this.lock()晕倒了别人就没法进去了;用信号灯可以外部释放,其他线程可以释放再获取sp.release()、sp.acquire()。

CyclicBarrier同步工具

        例如:组织人员(线程)郊游,约定一个时间地点(路障),人员陆续到达地点,等所有人员全部到达,开始到公园各玩各的,再到约定时间去食堂吃饭,等所有人到齐开饭……。

java.util.concurrent.CyclicBarrier

        一个同步辅助类,它允许一组线程互相等待,直到到达某个公共屏障点 (common barrier point)。在涉及一组固定大小的线程的程序中,这些线程必须不时地互相等待,此时 CyclicBarrier 很有用。因为该 barrier 在释放等待线程后可以重用,所以称它为循环 的 barrier。

        CyclicBarrier 支持一个可选的 Runnable 命令,在一组线程中的最后一个线程到达之后(但在释放所有线程之前),该命令只在每个屏障点运行一次。若在继续所有参与线程之前更新共享状态,此屏障操作很有用。

构造方法摘要

        1、CyclicBarrier(int parties):创建一个新的 CyclicBarrier,它将在给定数量的参与者(线程)处于等待状态时启动,但它不会在启动 barrier 时执行预定义的操作。

        2、CyclicBarrier(int parties, RunnablebarrierAction):创建一个新的 CyclicBarrier,它将在给定数量的参与者(线程)处于等待状态时启动,并在启动 barrier 时执行给定的屏障操作,该操作由最后一个进入 barrier 的线程执行。

方法摘要

        1、int await():在所有参与者都已经在此 barrier 上调用 await 方法之前,将一直等待。

        2、int await(longtimeout, TimeUnit unit):在所有参与者都已经在此屏障上调用 await 方法之前将一直等待,或者超出了指定的等待时间。

        3、int getNumberWaiting():返回当前在屏障处等待的参与者数目。

        4、int getParties():返回要求启动此 barrier 的参与者数目。

        5、boolean isBroken():查询此屏障是否处于损坏状态。

        6、void reset():将屏障重置为其初始状态。

程序实例

public class CyclicBarrierTest {

	public static void main(String[] args) {
		
		ExecutorService service = Executors.newCachedThreadPool();
		final CyclicBarrier cb = new CyclicBarrier(3);
		
		for (int i = 0; i < 3; i++) {
			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();
	}
}


CountDownLatch同步工具

        好像倒计时计数器,调用CountDownLatch对象的countDown方法就将计数器减1,当到达0时,所有等待者就开始执行。

java.util.concurrent.CountDownLatch

        一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。用给定的计数初始化 CountDownLatch。由于调用了 countDown() 方法,所以在当前计数到达零之前,await方法会一直受阻塞。之后,会释放所有等待的线程,await的所有后续调用都将立即返回。这种现象只出现一次——计数无法被重置。如果需要重置计数,请考虑使用 CyclicBarrier。

        CountDownLatch 是一个通用同步工具,它有很多用途。将计数 1 初始化的 CountDownLatch 用作一个简单的开/关锁存器,或入口:在通过调用 countDown() 的线程打开入口前,所有调用 await 的线程都一直在入口处等待。用 N 初始化的 CountDownLatch 可以使一个线程在 N 个线程完成某项操作之前一直等待,或者使其在某项操作完成 N 次之前一直等待。

        CountDownLatch 的一个有用特性是,它不要求调用 countDown 方法的线程等到计数到达零时才继续,而在所有线程都能通过之前,它只是阻止任何线程继续通过一个 await。

构造方法摘要

        1、CountDownLatch(int count):构造一个用给定计数初始化的 CountDownLatch。

方法摘要

        1、void await():使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断。

        2、boolean await(longtimeout, TimeUnit unit):使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断或超出了指定的等待时间。

        3、void countDown():递减锁存器的计数,如果计数到达零,则释放所有等待的线程。

        4、long getCount():返回当前计数。

        5、String toString():返回标识此锁存器及其状态的字符串。

程序实例

举例:多个运动员等待裁判命令:裁判等所有运动员到齐后发布结果。

/**
 * 
 * @Description: 模拟类似功能:多个运动员等待裁判命令:裁判等所有运动员到齐后发布结果。
 *
 * @author: zxt
 *
 * @time: 2018年4月16日 下午10:02:17
 *
 */
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);
		
		for (int i = 0; i < 3; i++) {
			// 产生3个运动员
			Runnable runnable = new Runnable() {
				public void run() {
					try {
						System.out.println("线程" + Thread.currentThread().getName() + "正准备接受命令");
						// 计数器为0继续向下执行
						cdOrder.await();
						
						System.out.println("线程" + Thread.currentThread().getName() + "已接受命令");
						// 模拟运动员运行
						Thread.sleep((long) (Math.random() * 10000));
						System.out.println("线程" + Thread.currentThread().getName() + "回应命令处理结果");
						// 跑到终点了,计数器减1
						cdAnswer.countDown();
						
					} catch (Exception e) {
						e.printStackTrace();
					}
				}
			};
			
			service.execute(runnable);
		}
		try {
			Thread.sleep((long) (Math.random() * 10000));
			
			// 裁判命令计数器置为0,发布命令
			System.out.println("线程" + Thread.currentThread().getName() + "即将发布命令");
			cdOrder.countDown();
			
			// 等待所有运动员,计数器为0 所有运动员到位
			System.out.println("线程" + Thread.currentThread().getName() + "已发送命令,正在等待结果");
			cdAnswer.await();
			System.out.println("线程" + Thread.currentThread().getName() + "已收到所有响应结果");
			
		} catch (Exception e) {
			e.printStackTrace();
		}
		
		service.shutdown();
	}
}


Exchanger同步工具

        用于实现两个人之间的数据交换,每个人在完成一定的事务后想与对方交换数据,第一个先拿出数据的人会一直等待第二个人,直到第二个人拿着数据到来时,才能彼此交换数据。

java.util.concurrent.Exchanger<V>

        V - 可以交换的对象类型

        可以在对中对元素进行配对和交换的线程的同步点。每个线程将条目上的某个方法呈现给 exchange 方法,与伙伴线程进行匹配,并且在返回时接收其伙伴的对象。Exchanger 可能被视为 SynchronousQueue 的双向形式。Exchanger 可能在应用程序(比如遗传算法和管道设计)中很有用。

        用法示例:以下是重点介绍的一个类,该类使用 Exchanger 在线程间交换缓冲区,因此,在需要时,填充缓冲区的线程获取一个新腾空的缓冲区,并将填满的缓冲区传递给腾空缓冲区的线程。

class FillAndEmpty {
	
	Exchanger<DataBuffer> exchanger = new Exchanger<DataBuffer>();
	DataBuffer initialEmptyBuffer = ...	a made-	up type
	DataBuffer initialFullBuffer = ...

	class FillingLoop implements Runnable {
		public void run() {
	       DataBuffer currentBuffer = initialEmptyBuffer;
	       try {
	         while (currentBuffer != null) {
	           addToBuffer(currentBuffer);
	           if (currentBuffer.isFull())
	             currentBuffer = exchanger.exchange(currentBuffer);
	         }
	         
	       } catch (InterruptedException ex) { 
	    	   ... handle ... 
	       }
		}
	}

	class EmptyingLoop implements Runnable {
		public void run() {
	       DataBuffer currentBuffer = initialFullBuffer;
	       try {
	         while (currentBuffer != null) {
	           takeFromBuffer(currentBuffer);
	           if (currentBuffer.isEmpty())
	             currentBuffer = exchanger.exchange(currentBuffer);
	         }
	         
	       } catch (InterruptedException ex) { 
	    	   ... handle ...
	       }
		}
	}

	void start() {
		new Thread(new FillingLoop()).start();
		new Thread(new EmptyingLoop()).start();
	}
}

构造方法摘要

        Exchanger():创建一个新的 Exchanger。

方法摘要

        1、V exchange(V x):等待另一个线程到达此交换点(除非当前线程被中断),然后将给定的对象传送给该线程,并接收该线程的对象。

        2、V exchange(V x, long timeout, TimeUnitunit):等待另一个线程到达此交换点(除非当前线程被中断,或者超出了指定的等待时间),然后将给定的对象传送给该线程,同时接收该线程的对象。

程序实例

举例:毒品交易,双方并不是同时到达,有先有后,只有都到达了,瞬间交换数据,各自飞。

public class ExchangerTest {

	public static void main(String[] args) {

		ExecutorService service = Executors.newCachedThreadPool();
		final Exchanger<String> exchanger = new Exchanger<String>();

		service.execute(new Runnable() {
			public void run() {
				try {
					String data1 = Thread.currentThread().getName() + Math.random() * 10000;
					System.out.println("线程" + Thread.currentThread().getName() + "正在把数据:" + data1 + "换出去");

					Thread.sleep((long) (Math.random() * 1000));
					String data2 = (String) exchanger.exchange(data1);
					System.out.println("线程" + Thread.currentThread().getName() + "换回的数据为:" + data2);

				} catch (Exception e) {

				}
			}
		});
		
		service.execute(new Runnable() {
			public void run() {
				try {
					String data1 = Thread.currentThread().getName() + Math.random() * 10000;
					System.out.println("线程" + Thread.currentThread().getName() + "正在把数据:" + data1 + "换出去");

					Thread.sleep((long) (Math.random() * 1000));
					String data2 = (String) exchanger.exchange(data1);
					System.out.println("线程" + Thread.currentThread().getName() + "换回的数据为:" + data2);

				} catch (Exception e) {

				}
			}
		});
	}
}

猜你喜欢

转载自blog.csdn.net/zengxiantao1994/article/details/79967868