【并发编程学习】CyclicBarrier 栅栏的简单使用

简单概述

CyclicBarrier 是规定了多少个线程都到达某个位置后,才会把这些线程放行。
举了例子,工作中我们都会有开会的场景,比如说我们需要等所有人都到齐才把会开起来,CyclicBarrier 就提供了模拟这种场景的工具供我们使用。
在讲解 CyclicBarrier 之前,先要讲一下 CyclicBarrier 和 CountDownLatch 的区别,他们最大的区别就是 CyclicBarrier 是一组线程,一组线程的去执行,而 CountDownLatch 是一次性的,不能循环往复的使用。
CyclicBarrier 就是循环往复的使用,你可以理解成,今天八点开会,明天八点继续开会。
CountDownLatch 是今天八点开会,只开一次,后面就不开了。


CyclicBarrier 创建

CyclicBarrier 在创建时,有两种构造函数,第一种构造函数中有一个参数,参数是一个数字,这个数字代表有多少个线程都到达指定位置后,才开始走后续的操作,代码如下:

CyclicBarrier cyclicBarrier = new CyclicBarrier(3);

第二种构造函数是有两个参数,第一个参数是一个数字,第二个参数是一个Runnable,数字跟上述一样,代表多少个线程都到达指定位置后才开始走后续的操作,而第二个参数Runnable是最后一个到达的线程做哪些事情,代码如下:

CyclicBarrier cyclicBarrier = new CyclicBarrier(3, new Runnable() {
    
    
	@Override
	public void run() {
    
    
		System.out.println("最后一个线程去做的事情");
	}
});

其实 CyclicBarrier 创建时给的数字就是一个计数器,告诉 CyclicBarrier 你最多能拦截多少个线程,多余的线程等待。


CyclicBarrier 函数

现在我们知道了怎么创建,下面我们讲几个常用的函数,以下的几个函数讲解,你不妨先把代码示例放在你的工具中跑一下,这样能够快速明白这个函数的意思和用处。

await()

await() 函数是当某个线程到达指定位置后报告给 CyclicBarrier,告诉 CyclicBarrier 我已经到达指定位置,并已经在这个位置等待其他线程,当规定的线程数都到后,CyclicBarrier 就会放行所有的线程。
内部原理是调用 await() 函数后,CyclicBarrier 内部中会将计数器的数量减一,当计数器到达零后,所有的线程才会执行。

代码示例:

public static void main(String[] args) throws InterruptedException {
    
    
	CyclicBarrier cyclicBarrier = new CyclicBarrier(2);
	new Thread(() -> {
    
    
		System.out.println("一号 到达");
		try {
    
    
			cyclicBarrier.await();
			System.out.println("等所有线程都到达后, 一号执行的事情");
		} catch (Exception e) {
    
    
			e.printStackTrace();
		}
	}).start();

	new Thread(() -> {
    
    
		System.out.println("二号 到达");
		try {
    
    
			cyclicBarrier.await();
			System.out.println("等所有线程都到达后, 二号执行的事情");
		} catch (Exception e) {
    
    
			e.printStackTrace();
		}
	}).start();
}

执行结果:

一号 到达
二号 到达
等所有线程都到达后, 二号执行的事情
等所有线程都到达后, 一号执行的事情

await(long timeout, TimeUnit unit)

await(long timeout, TimeUnit unit) 是某一个线程最多等待多长时间,如果等待时间超过了设定的时间就推翻 CyclicBarrier 并抛出异常。
推翻在这里做一下解释,引用上面简单概述的例子来讲,就是领导在开会地点等待过程中,因为等待时间太长,就把此次会议解散,去做其他的事情了,而没有到的那个人等后续到达了开会地点,发现开会的人都不在这里了,就也去做其他事情了。
根据下面的代码案例和打印结果,就能看到因为某个线程因为等待时间过长,就推翻了 CyclicBarrier,另一个线程在后续到达指定地方后直接抛出异常的结果。
另外需要注意的是,下面示例中一号线程抛出的异常是TimeoutException,二号线程抛出的是BrokenBarrierException

代码示例:

public static void main(String[] args) throws InterruptedException {
    
    
	CyclicBarrier cyclicBarrier = new CyclicBarrier(2);
	new Thread(() -> {
    
    
		System.out.println("一号 到达");
		try {
    
    
			// 一号线程只等待一秒
			cyclicBarrier.await(1000, TimeUnit.MILLISECONDS);
			System.out.println("等所有线程都到达后, 一号执行的事情");
		} catch (Exception e) {
    
    
			e.printStackTrace();
			System.out.println("一号 因为等待时间太久, 推翻了 CyclicBarrier");
		}
	}).start();

	new Thread(() -> {
    
    
		System.out.println("二号 到达");
		try {
    
    
			// 二号线程等待两秒
			Thread.sleep(2000);
			cyclicBarrier.await();
			System.out.println("等所有线程都到达后, 二号执行的事情");
		} catch (Exception e) {
    
    
			e.printStackTrace();
			System.out.println("二号 因为同组线程推翻了 CyclicBarrier 抛出了异常");
		}
	}).start();
}

执行结果:

一号 到达
二号 到达
java.util.concurrent.TimeoutException
	at java.util.concurrent.CyclicBarrier.dowait(CyclicBarrier.java:257)
	at java.util.concurrent.CyclicBarrier.await(CyclicBarrier.java:435)
	at com.user.test.CyclicBarrierTest.lambda$main$0(CyclicBarrierTest.java:51)
	at java.lang.Thread.run(Thread.java:748)
一号 因为等待时间太久, 推翻了 CyclicBarrier
java.util.concurrent.BrokenBarrierException
	at java.util.concurrent.CyclicBarrier.dowait(CyclicBarrier.java:207)
	at java.util.concurrent.CyclicBarrier.await(CyclicBarrier.java:362)
	at com.user.test.CyclicBarrierTest.lambda$main$1(CyclicBarrierTest.java:65)
	at java.lang.Thread.run(Thread.java:748)
二号 因为同组线程推翻了 CyclicBarrier 抛出了异常

isBroken()

isBroken() 是判断 CyclicBarrier 是否被推翻。
引用讲解 await(long timeout, TimeUnit unit) 的例子,就能知道这个函数的作用。

reset()

reset() 函数是重置 CyclicBarrier 回到初始状态。
用下面的代码示例来讲一下这个函数的意思,先是创建了一个有 2 个线程数的 CyclicBarrier,然后一号线程开始启动,让一号线程最多等待时间是一秒钟,如果一秒钟以内其他线程未到指定地点,推翻 CyclicBarrier 并抛出异常,而在异常处理中我再重置这个 CyclicBarrier,这里你可以理解为,一个人去参加某活动,但是这个活动需要两个人一块参加,先到的人需要等后面的人补上,凑成两个人才能走,但是因为第一个先到的人因为耐不住性子,等待时间久了以后就走掉了,让出了这个位子。

代码示例:

public static void main(String[] args) throws InterruptedException {
    
    
	// 创建一个只有2个线程数量的 CyclicBarrier
	CyclicBarrier cyclicBarrier = new CyclicBarrier(2);
	new Thread(() -> {
    
    
		System.out.println("一号 到达");
		try {
    
    
			// 一号线程只等待一秒
			cyclicBarrier.await(1000, TimeUnit.MILLISECONDS);
			System.out.println("等所有线程都到达后, 一号执行的事情");
		} catch (Exception e) {
    
    
 			e.printStackTrace();
 			// 让出活动位置
			cyclicBarrier.reset();
			System.out.println("一号 因为嫌等待时间太久, 让出了活动位置");
		}
	}).start();

	new Thread(() -> {
    
    
		try {
    
    
			// 二号线程等待两秒
			Thread.sleep(2000);
			System.out.println("二号 到达");
			cyclicBarrier.await();
			System.out.println("二号参加活动");
		} catch (Exception e) {
    
    
			e.printStackTrace();
		}
	}).start();

	new Thread(() -> {
    
    
		try {
    
    
			// 三号线程等待两秒
			Thread.sleep(2000);
			System.out.println("三号 到达");
			cyclicBarrier.await();
			System.out.println("三号参加活动");
		} catch (Exception e) {
    
    
			e.printStackTrace();
		}
	}).start();
}

执行结果:

一号 到达
java.util.concurrent.TimeoutException
	at java.util.concurrent.CyclicBarrier.dowait(CyclicBarrier.java:257)
	at java.util.concurrent.CyclicBarrier.await(CyclicBarrier.java:435)
	at com.user.test.CyclicBarrierTest.lambda$main$0(CyclicBarrierTest.java:52)
	at java.lang.Thread.run(Thread.java:748)
一号 因为嫌等待时间太久, 让出了活动位置
二号 到达
三号 到达
三号参加活动
二号参加活动




End


猜你喜欢

转载自blog.csdn.net/weixin_43657300/article/details/128297297