两种线程“等待”,“门闩”和“栅栏”

CountDownLatch(门闩)和CyclicBarrier(栅栏)都是java.concurrent里的多线程控制工具类,我这里说他们两个都是控制线程等待,它们都像是一个计数器,让某一个线程等待计数完成后再执行,但是又有些不同,CountDownLatch是通过计数完成后才执行线程来控制线程等待,而CyclicBarrier可以是线程执行到某一处后等待计数,计数完成后再继续执行。

简单来说,CountDownLatch控制线程等待,是在做好所有准备后,才执行线程,就像早上起来我们会刷牙洗脸收拾好所有东西吃完早餐再出门一样。CyclicBarrier则不是“整装待发”后再出门,你可能洗漱好收拾好所有东西后便出门了,然后等待去到面包店买了面包再吃早餐。我感觉CyclicBarrier才是真正的让线程等待。

CountDownLatch使用起来比较简单,构造函数里只有一个参数就是设置这个计数器的计数个数。

Public CountDownLatch(int count)

package countdownlatch;

import java.util.concurrent.CountDownLatch;
import java.util.Random;

public class CountDownLatchDemo implements Runnable {

	static final CountDownLatch CDL = new CountDownLatch(10); //计数数量为10
	static final CountDownLatchDemo CDLdemo = new CountDownLatchDemo();
	@Override
	public void run() {
		try {
			Thread.sleep(new Random().nextInt(10)*1000);
			System.out.println(Thread.currentThread().getName()+" complete.");
			CDL.countDown(); //计数器减一
		} catch(InterruptedException e) {
			e.printStackTrace();
		}
	}
	
	public static void main(String[] args) throws InterruptedException {
		Thread arr[] = new Thread[10];
		for(int i=0; i<10; i++) {
			arr[i] = new Thread(CDLdemo);
			arr[i].start();
		}
		
		//计数器等待,直到倒计时完成
		CDL.await();
		System.out.println("All Threads complete.");
	}

}

代码第8行实例化一个CountDownLatch计数器,计数值传入10,即需要等待10个线程完成任务后,在CountDownLatch上等待的线程才能执行,代码第16行,每当一个线程完成自己的任务后,就调用CountDownLatch的countDown( )方法,表示让CountDownLatch的计数值减1。主线程中则调用CountDownLatch的await( )方法表示要求主线程在CountDownLatch上等待,等待CountDownLatch中的所有任务执行完成后,再继续执行自己的任务。

 

到循环栅栏CyclicBarrier,同样可以实现计数让线程之间等待,为什么叫循环栅栏,因为CyclicBarrier实例化后可以循环使用,假设我们设置了计数值为10,那么在计数值达到10后,会自动清零,可以继续使用。CyclicBarrier的构造函数有两个参数:

Public CyclicBarrier(int parties, Runnable barrierAction)

第一个参数parties表示计数值,第二个参数barrierAction表示计数值达到后执行的动作,这样明显比CountDownLatch好一些。来看看CyclicBarrier具体怎么用:

package cyclicbarrier;

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

public class CyclicBarrierDemo {
	
	public static class Student implements Runnable {
		//实例化循环栅栏
		private final CyclicBarrier cyclic;
		
		//构造函数初始化
		Student(CyclicBarrier cyclic) {
			this.cyclic = cyclic;
		}
		
		//模拟线程到达
		void ThreadReady() {
			try {
				Thread.sleep(Math.abs(new Random().nextInt()%10000));
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName()+" ready.");
		}
		//模拟线程工作
		void ThreadStart() {
			try {
				Thread.sleep(Math.abs(new Random().nextInt()%10000));
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName()+" complete");
		}
		
		@Override
		public void run() {
			try {
				ThreadReady(); //所有线程开始到达
				cyclic.await(); //等待所有线程到达
				ThreadStart(); //所有线程开始运行
				cyclic.await(); //等待所有线程完成
			} catch (InterruptedException e) {
				e.printStackTrace();
			} catch (BrokenBarrierException e) {
				e.printStackTrace();
			}
		}
	}
	
	public static class BarrierRun implements Runnable {
		boolean flag;
		int i;
		//构造函数初始化
		BarrierRun(boolean flag, int i) {
			this.flag = flag;
			this.i = i;
		}
		
		@Override
		public void run() {
			if(flag) {
				System.out.println("Thread "+i+"个,所有线程完成.");
			} else {
				System.out.println("Thread "+i+"个,线程到达.");
				flag = true;
			}
		}
	}
	
	public static void main(String[] args) {
		boolean flag = false;
		int i =10;
		Thread arr[] = new Thread[i];
		BarrierRun br = new BarrierRun(flag, i);
		/*CyclicBarrier的构造函数:public CyclicBarrier(int parties, Runnable barrierAction)
		 * 第一个参数parties是计数个数,第二个参数是当计数器一次计数完成后,系统会执行的动作.
		 */
		CyclicBarrier cyclic = new CyclicBarrier(i, br);
		for(int j=0; j<i; j++) {
			arr[j] = new Thread(new Student(cyclic));
			arr[j].start();
		}

	}

}

首先第9到50行是我们模拟线程要做的事,第18到26行,线程睡眠随机时间模拟不同线程的不同到达时间,第27到35行同样线程睡眠随机时间来模拟不同线程完成任务的消耗时间。到了第40行,线程到达后栅栏cyclic就调用await()方法,意思是线程到这里后边在CyclicBarrier上等待,等到所有线程到达后,才继续执行。当所有线程到达后,便继续往下,到第42行模拟线程完成任务,每个线程完成任务后都会再次进入第43行的cyclic.await()等待,意指等待所有线程完成任务。

      我们先继续往下看,第52到70行的BarrierRun函数是用来传入CyclicBarrier的第二个参数,即当CyclicBarrier完成一次完整计数后系统要执行的动作,这里BarrierRun函数要做的就是当cyclic计数完成后根据输出相应的提示语句,当flag为true时提示所有线程完成任务,false时提示所有线程到达完毕。

      一切准备就绪后就来看主线程,第76行实例化一个BarrierRun对象br,传入参数false和线程数i,然后第80行实例化一个CyclicBarrier对象cyclic,传入参数i是计数值,br即计数完成后要执行的动作。接着让10个线程陆续执行即可,每个线程中都会“被”cyclic等待两次,第一次是等待10个线程到达,10个线程都到达后,执行cyclic的执行动作BarrierRun,也就是输出线程到达提示,接着线程继续执行,随机时间睡眠后,会继续进入cyclic等待,等待所有线程的执行完毕后,输出语句。

仔细观察的你可能会发现代码第44行开始要抛出两个异常:

第一个异常InterruptedException就是说在等待过程中线程被中断,按我在书上看到的说法:“大部分迫使线程等待的方法都可能会抛出这个异常,使得线程在等待时依然可以响应外部紧急事件。”第二个异常BrokenBarrierException,这个异常表示当前的CyclicBarrier已经破损,按书上说法:“如果继续等待,可能是徒劳无功的,因为可能系统已经没有办法等待所有线程到期了。“遇到这个异常如果不抛出,可能会现入无止境的等待中。就像线程睡眠时一定要抛出异常一样,使用CyclicBarrier等待线程记得一定要抛出BrokenBarrierException异常。

完整代码已上传GitHub:

https://gitee.com/justinzeng/codes/fy4whmn2u3qgpr0lsoxac21

https://github.com/justinzengtm/Multithreading/blob/master/CyclicBarrierDemo.java

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值