试用栅栏 CyclicBarrier,CountDownLatch以及异常BrokenBarrierException

本文通过实例详细解析了CyclicBarrier与CountDownLatch的工作原理及其应用场景。CyclicBarrier用于同步多个线程,直到所有线程到达指定点后才允许继续执行;CountDownLatch则作为计数器,待计数器归零时解除线程阻塞。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

参考:点击打开链接

CyclicBarrier试用及BrokenBarrierException

CyclicBarrier(int parties, Runnable barrierAction)

通俗的说,它就是一个栅栏,所有线程到了栅栏后必须等待,直到凑够了parties个数量的线程,然后这些线程才可以突破栅栏,继续做事情。否则就等着。

(图片来自参考)

例子:
package threadTest;

import java.util.Random;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

public class TestCyclicBarrier implements Runnable{
	final static int ThreadNUM=10;//线程数
	final static int parties=ThreadNUM;//栅栏的parties参数。第二个实验改为6.
	
	//突破栅栏时执行的方法。
	static Runnable barrrierAction1=new Runnable(){
		public void run(){
			System.out.println(Thread.currentThread().getName()+"tripped first barrier");
		}
	};
	static Runnable barrrierAction2=new Runnable(){
		public void run(){
			System.out.println(Thread.currentThread().getName()+"tripped second barrier");
		}
	};
	
	static CyclicBarrier barrier1 =new CyclicBarrier(parties,barrrierAction1);
	static CyclicBarrier barrier2=new CyclicBarrier(parties,barrrierAction2);
	
	
	@Override
	public void run() {
		try {
			Random random=new Random();
			
			int time1=1000+random.nextInt(2000);//1000~3000
			Thread.sleep(time1);
			System.out.println(Thread.currentThread().getName()+":first barrie. num of"+barrier1.getNumberWaiting()+" waiting");
			
			barrier1.await();//第一个栅栏
//			barrier1.await(4,TimeUnit.SECONDS);
			
			int time2=1000+random.nextInt(2000);//1000~3000
			Thread.sleep(time2);
			System.out.println(Thread.currentThread().getName()+":second barrie. num of"+barrier2.getNumberWaiting()+" waiting");
			barrier2.await();//第二个栅栏
//			barrier1.await(4,TimeUnit.SECONDS);
		} catch (InterruptedException e) {
			e.printStackTrace();
		} catch (BrokenBarrierException e) {
			e.printStackTrace();
		}

	}
	
	public static void main(String[] args) {
		for(int i=0;i<ThreadNUM;i++){
			TestCyclicBarrier test=new TestCyclicBarrier();
			Thread t=new Thread(test);
			t.start();
		}
	}
}



打印:
Thread-8:first barrie. num of0 waiting
Thread-6:first barrie. num of1 waiting
Thread-4:first barrie. num of2 waiting
Thread-0:first barrie. num of3 waiting
Thread-7:first barrie. num of4 waiting
Thread-1:first barrie. num of5 waiting
Thread-9:first barrie. num of6 waiting
Thread-5:first barrie. num of7 waiting
Thread-3:first barrie. num of8 waiting
Thread-2:first barrie. num of9 waiting
Thread-2tripped first barrier
Thread-4:second barrie. num of0 waiting
Thread-3:second barrie. num of1 waiting
Thread-0:second barrie. num of2 waiting
Thread-8:second barrie. num of3 waiting
Thread-1:second barrie. num of4 waiting
Thread-2:second barrie. num of5 waiting
Thread-7:second barrie. num of6 waiting
Thread-6:second barrie. num of6 waiting
Thread-9:second barrie. num of8 waiting
Thread-5:second barrie. num of9 waiting
Thread-5tripped second barrier

存在问题:如果把栅栏的 parties改为6个,程序死在那。

我猜测原因是
CyclicBarrier(int parties)如果 parties小于线程数的话,一部分线程会先突破。后面的被阻挡的线程由于可能凑不够突破数而一直阻塞·,程序无法结束。

把栅栏的parties改成6。有6个线程先突破了。有4个线程没办法突破,因为凑不齐突破条件的6个。

然后发现方法:
intawait(long timeout, TimeUnit unit)

可以带时间参数。超时抛出TimeoutException。想增加个检测超时的,不让程序死。

把两个 barrier.await()改成
barrier.await(4,TimeUnit.SECONDS); //4秒不能突破的话抛出Timeout异常

运行。然后抛出异常


BrokenBarrierException
If any thread is interrupted while waiting, then all other waiting threads will throw BrokenBarrierException and the barrier is placed in the broken state.
翻译下:某个等待的线程被中断,其它等待的线程会抛出这个异常

1个因为超时中断,3个线程抛出BrokenBarrierException。原因显而易见。


闭锁CountDownLatch试用

就是个计数锁。
CountDownlatch countDownLatch=new CountDownlatch (count);
count代表计数器的大小。当其减少到0时,解除阻塞继续执行代码。

如,一个线程A中
countDownLatch.await();//阻塞
doSomething();

其它线程可以按下计数器,让计数器减1。
countDownLatch.countDown();
然后接着做自己的事情。

线程A的闭锁计数器减少到0时,就解除阻塞,继续做事。

await还有种形式:
boolean await(long timeout, TimeUnit unit)
对于它的返回值
Returns:
true  if the count reached zero and  false  if the waiting time elapsed before the count reached zero
翻译一下
超时的话,如果计数器不为0( 按计数器的线程没达到期望的数量),返回false,解除阻塞。为0的话( 按计数器的 线程达到了期望的数量),返回true。

关于CountDownLatch与CyclicBarrier的区别,这篇文章表达很生动。
个人总结:一个是栅栏,一个是计数锁。

下面是自己写的一个关于CountDownLatch使用的生动小例子。如有雷同,她抄我的。
例子:StudentNum 个学生考试。交卷交晚了和中途弃考的考生会不及格。 对待交卷交完的同学,收卷老师很耐心,不会暴力地抓卷,而是耐心地等她做完交卷,然后给她不及格。有个学生会受神秘力量影响,神经错乱大闹考场(被中断了)。但是大家精力都很集中,不会受他的影响而中断考试。
package threadTest;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

public class TestCountDownLatch {
	final static int StudentNum = 20;// 线程数
	static volatile boolean isInExam = false;// 是否在考试时间内
	static Map<String, Long> timeUse = new ConcurrentHashMap<>();// 记录交卷时间
	static Set<String> lateSet = new CopyOnWriteArraySet<String>();// 交卷交晚了的学生

	public static void main(String[] args) {
		TestCountDownLatch test = new TestCountDownLatch();
		// 耐心的收卷老师。会等所有考生提交了卷子,而不是抓卷。
		CountDownLatch teacher = new CountDownLatch(StudentNum); 
		// 考试开始的信号,不准提前写卷子。
		CountDownLatch startExamSignal = new CountDownLatch(1);
		// 考试结束的信号,没按时间交卷的同学会被阴险的老师记到lateSet。如果都交卷了,考试提前结束。
		CountDownLatch endExamSignal = new CountDownLatch(StudentNum);

		Thread[] threadList = new Thread[StudentNum];
		// 考生进场
		for (int i = 0; i < StudentNum; i++) {
			threadList[i] = new Thread(test.new Student(
					Integer.toString(10000 + i), startExamSignal, endExamSignal,teacher));
			threadList[i].start();
		}

		// 好像有考生今天不太对劲
		Random random = new Random();
		new Thread(test.new UnexpectedEvent(
				threadList[random.nextInt(StudentNum - 1)], startExamSignal))
				.start();
		isInExam = true;
		// 考试开始!
		System.out.println("老师: 考试开始!");
		startExamSignal.countDown();

		// 老师无聊地监考
		try {
			isInExam = endExamSignal.await(2, TimeUnit.SECONDS);// 考试时间2秒
			System.out.println("老师:考试结束了!!不要写了!!");
			//老师收拾好了所有卷子
			teacher.await();
			System.out.println("老师:下面宣布中途弃考和没按时交卷的考生!!直接不及格!!");
			System.out.println(lateSet);
		} catch (InterruptedException e) {
			System.out.println("考试居然中断了!!!");
			e.printStackTrace();
		}
	}

	class Student implements Runnable {
		private String testNum;// 考号
		private CountDownLatch startExamSignal;// 考试开始的信号
		private CountDownLatch endExamSignal;// 考试结束的信号
		private CountDownLatch teacher;// 收卷老师

		public Student(String testNum, CountDownLatch startExamSignal,
				CountDownLatch endExamSignal,CountDownLatch teacher) {
			this.testNum = testNum;
			this.startExamSignal = startExamSignal;
			this.endExamSignal=endExamSignal;
			this.teacher = teacher;
		}

		@Override
		public void run() {
			Random random = new Random();
			int time1 = 1000 + random.nextInt(2000);// 1000~3000
			long startTime=0;
			try {
				startExamSignal.await();// 不准提前答卷。
				startTime=System.currentTimeMillis();
				Thread.sleep(time1);// 写卷子	
//				System.out.println(teacher.getCount());
			} catch (InterruptedException e) {
				System.out.printf(
						"学生%s:电子竞技处于水深火热中!劳资不考了!" + System.lineSeparator(),testNum);
				//监考人员把他记到不合格名单中。
				lateSet.add(testNum);
				
				e.printStackTrace();
			} finally {
				timeUse.put(testNum,System.currentTimeMillis()-startTime);
				//交卷交晚的,记到不合格名单中。
				if (!isInExam) {
					lateSet.add(testNum);
				}
				System.out.printf("考生%s提交了卷子" + System.lineSeparator(), testNum);
				teacher.countDown();// 交卷给收卷老师
				endExamSignal.countDown();
			}
		}
	}

	// 神秘力量挑选一个学生让他神经错乱(中断)
	class UnexpectedEvent implements Runnable {
		private Thread unfortunatelyStudent;
		private CountDownLatch startExamSignal;// 考试开始的信号

		UnexpectedEvent(Thread unfortunatelyStudent,
				CountDownLatch startExamSignal) {
			this.unfortunatelyStudent = unfortunatelyStudent;
			this.startExamSignal = startExamSignal;
		}

		@Override
		public void run() {
			try {
				Random random = new Random();
				long timeStart = 1200 + random.nextInt(400);
				
				startExamSignal.await();
				long time = System.currentTimeMillis();
				while (System.currentTimeMillis() - time < timeStart) {
				}
				//中断一个线程
				unfortunatelyStudent.interrupt();
			} catch (InterruptedException e) {
				System.out.println("就当啥事都没发生过");
				e.printStackTrace();
			}
		}
	}
}


打印:


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值