并发技术_3_CountDownLatch

本文详细介绍了CountDownLatch类的使用方法及应用场景,通过四个示例演示了如何利用CountDownLatch控制线程同步,确保线程以特定顺序执行。

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

CountDownLatch

 

Latch门栓,也就是有"门锁"的功能;

当门没有打开时,N个人是不能进入屋内的,也就是N个线程是不能继续往下运行的,支持这样的特性可以控制线程执行任务的时机,使线程以"组团"的方式一起执行任务。

 

CountDownLatch所提供的功能是判断count计数不为0时,则当前线程呈wait状态,也就是在屏障处等待。

CountDownLatch也是一个同步功能的辅助类,使用效果是给定一个计数,当使用这个CountDownLatch类的线程计数不为0时,则呈wait状态,如果为0时则继续运行。

 

实现等待与继续运行的效果分别需要使用await()countDown()方法来进行。

调用await()方法时判断计数是否为0,如果不为0则呈等待状态。

其他线程可以调用countDown()方法将计数减1,当计数减到0时,呈等待的线程继续运行。

而方法getCount()就是获得当前的计数个数。

 

CountDownLatch这个类能够使一个线程等待其他线程完成各自的工作后再执行。

(例如:应用程序的主线程希望在 负责启动框架服务的线程已经启动所有的框架服务之后 再执行)

 

CountDownLatch的作用与Thread.join()方法类似,可用于一组线程和另外一组线程的协作。

 

await():调用此方法会一直阻塞当前线程,直到计时器为0

countDown():当线程调用此方法,计数减一。

 

场景:

运动会赛跑,裁判员要等所有的运动员到达起跑线才开始准备。

初始化CountDownLatch,同时设置计数器的数量(阀值),一个运动员到达,计数器减1,当计数器为0,说明所有运动员到场,

于是看是运行后面的内容

 

 

 

 

初步使用

这个Demo实现 CountDownLatch的计数不为0时,线程呈阻塞状态。

public class MyService {

	private CountDownLatch down = new CountDownLatch(1);

	public void testMethod() {

	try {

	System.out.println("A");
	down.await();
	System.out.println("B");

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

	}

	public void downMethod() {
			System.out.println("X");
			down.countDown();
	}

}

public class MyThread extends Thread {

	private MyService service;

	public MyThread(MyService service) {
			this.service = service;
	}

	@Override
	public void run() {
			service.testMethod();
	}

}

public class Run {

	public static void main(String[] args) throws Exception {

	MyService service = new MyService();

	MyThread t = new MyThread(service);
	t.start();

	Thread.sleep(2000);
	service.downMethod();

	}

}

输出结果:
	A
	X
	B
	

我们来分析下代码:

  1. main方法启动,执行Thread线程,线程运行MyService类中的testMethod()方法,输出"A"
  2. 执行到down.await();时,线程阻塞,开始等count计数减少为0
  3. 上面执行的同时,main方法接着往下走,睡眠了两秒,然后调用MyService类的downMethod()方法
  4. 输出"X"
  5. 这时候,执行down.countDown(),CountDownLatch的计数器减1,于是CountDownLatch计数器变为0
  6. 于是,阻塞在testMethod()方法中的Thread线程 输出"B"

 

 

 

 

第二个例子

模拟一个栗子:

裁判员要等到所有运动员全部都集中后,才继续往下运行。

论证多个线程 同步点之间 阻塞的特点,线程必须都到达同步点之后才可以继续向下运行。

public class MyThread extends Thread {

	private CountDownLatch maxRunner;

	public MyThread(CountDownLatch maxRunner) {
			this.maxRunner = maxRunner;
	}

	@Override
	public void run() {

	try {

	System.out.println(Thread.currentThread().getName());

	Thread.sleep(2000);
	maxRunner.countDown();

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

	}

}

public class Run {

	public static void main(String[] args) throws Exception {

	CountDownLatch maxRuner = new CountDownLatch(10);
	MyThread[] tArr = new MyThread[Integer.parseInt("" + maxRuner.getCount())];

	for (int i = 0; i < tArr.length; i++) {
			tArr[i] = new MyThread(maxRuner);
			tArr[i].setName("线程" + (i + 1));
			tArr[i].start();
	}
	maxRuner.await();
	System.out.println("都回来了");

	}

}

输出结果:
	线程1
	线程2
	线程3
	线程4
	线程6
	线程5
	线程8
	线程7
	线程9
	线程10
	都回来了
	

让我们来捋一下代码:

  1. 我们将计数器设为10,然后创建10个线程。
  2. 迭代线程,每个线程同时执行,在线程中我们将计数器减1,并且睡两秒
  3. 主线程要等计数器为0的时候,才能继续往下走
  4. 最后10个线程,全部循环完成,计数器归零,然后输出"都回来了"

 

 

 

 

第三个栗子

场景:裁判员要等待所有运动员各就各位以后,再开始比赛

public class MyService {

	private CountDownLatch down = new CountDownLatch(1);

	public void testMethod() {

	try {

	System.out.println(Thread.currentThread().getName() + " 准备");
	down.await();
	System.out.println(Thread.currentThread().getName() + " 结束");

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

	}

	public void downMethod() {
			System.out.println("开始");
			down.countDown();
	}

}

public class MyThread extends Thread {

	private MyService service;

	public MyThread(MyService service) {
			this.service = service;
	}

	@Override
	public void run() {
			service.testMethod();
	}

}

public class Run {

	public static void main(String[] args) throws Exception {

	MyService service = new MyService();

	MyThread[] tArr = new MyThread[10];
	for (int i = 0; i < 10; i++) {
			tArr[i] = new MyThread(service);
			tArr[i].setName("线程 " + (i + 1));
			tArr[i].start();
	}

	Thread.sleep(2000);
	service.downMethod();

	}

}

输出结果:
	线程 1 准备
	线程 3 准备
	线程 4 准备
	线程 5 准备
	线程 2 准备
	线程 7 准备
	线程 8 准备
	线程 6 准备
	线程 9 准备
	线程 10 准备
	开始
	线程 1 结束
	线程 3 结束
	线程 4 结束
	线程 5 结束
	线程 2 结束
	线程 7 结束
	线程 8 结束
	线程 6 结束
	线程 10 结束
	线程 9 结束
	

捋一下程序:

  1. Run类中创建一个MyService对象,这时候,计数器中数量为1
  2. 然后创建10个线程,同时启动这10个线程。
  3. 每个线程执行输出"准备"后,因为await()方法,开始阻塞。
  4. 10个线程,全部进入阻塞状态。
  5. 主线程继续往下走,睡两秒,然后执行downMethod()方法。
  6. 这时候,计数器减一。
  7. 接下来所有阻塞的线程,输出"结束"

 

 

这个Demo有个问题,虽然运行成功,但不能保证在main主线程执行了service.downMethod()时,所有的工作线程都呈wait状态,因为某些线程有可能准备的时间较长,可能耗时超过两秒,这时,如果在第两秒调用service.downMethod()方法,就达不到"唤醒所有线程"

继续往下运行的目的了,也就是说,裁判员没有等全部的运动员到来时,就让发令枪响起来开始比赛了。

 

下面我们将这个Demo修改一下。

 

 

 

 

第四个栗子

我们使用CountDownLatch类来实现"所有线程"wait后再同一唤醒的效果,此Demo大量使用CountDownLatch类来实现业务要求的同步效果。

public class MyThread extends Thread {

	// 裁判等待所有运动员到来
	private CountDownLatch comingTag;
	// 等待裁判说准备开始
	private CountDownLatch waitTag;
	// 等待起泡
	private CountDownLatch waitRunTag;
	// 起跑
	private CountDownLatch beginTag;
	// 所有运动员到达终点
	private CountDownLatch endTag;

	public MyThread(CountDownLatch comingTag, CountDownLatch waitTag, CountDownLatch waitRunTag, CountDownLatch beginTag, CountDownLatch endTag) {
			this.comingTag = comingTag;
			this.waitTag = waitTag;
			this.waitRunTag = waitRunTag;
			this.beginTag = beginTag;
			this.endTag = endTag;
	}

	@Override
	public void run() {
			try {

	System.out.println("运动员使用不同交通工具不同速度到达起跑重点,正在向这边走");
	Thread.sleep((int) Math.random() * 10000);
	System.out.println(Thread.currentThread().getName() + " 到达起跑点...");

	comingTag.countDown();

	System.out.println("等待裁判说准备..");

	waitTag.await();

	System.out.println("各就各位,准备起跑..");

	Thread.sleep((int) Math.random() * 10000);

	waitRunTag.countDown();
	beginTag.await();

	System.out.println(Thread.currentThread().getName() + "运动员起跑,并且比赛过程用时不确定..");

	Thread.sleep((int) Math.random() * 10000);
	endTag.countDown();

	System.out.println(Thread.currentThread().getName() + "运动员到达终点");

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

}

public class Run {

	public static void main(String[] args) {

	try {

	CountDownLatch comingTag = new CountDownLatch(10);
	CountDownLatch waitTag = new CountDownLatch(1);
	CountDownLatch waitRunTag = new CountDownLatch(10);
	CountDownLatch beginTag = new CountDownLatch(1);
	CountDownLatch endTag = new CountDownLatch(10);

	MyThread[] tArr = new MyThread[10];
	for (int i = 0; i < 10; i++) {
			tArr[i] = new MyThread(comingTag, waitTag, waitRunTag, beginTag, endTag);
			tArr[i].start();
	}

	System.out.println("裁判员在等待选手到来");
	comingTag.await();

	System.out.println("裁判员看到所有运动员来了,各就各位前巡视,用时5秒");
	Thread.sleep(5000);
	waitTag.countDown();

	System.out.println("各就各位...");
	waitRunTag.await();
	Thread.sleep(2000);

	System.out.println("发令枪响");
	beginTag.countDown();
	endTag.await();
	System.out.println("所有运动员到达,统计比赛名次");

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

	}

}


 

 

 

 

方法await(long timeout,TimeUnit unit)

此方法的作用是使线程在指定的最大时间单位内进入WAITING状态,如果超过这个时间则自动唤醒,程序继续向下运行。

参数timeout是等待的时间,unit参数是时间的单位。


JDK API

使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断或超出了指定的等待时间。

如果当前计数为零,则此方法立刻返回 true 值。

如果当前计数大于零,则出于线程调度目的,将禁用当前线程,且在发生以下三种情况之一前,该线程将一直处于休眠状态:

  • 由于调用 countDown() 方法,计数到达零;或者
  • 其他某个线程中断当前线程;或者
  • 已超出指定的等待时间。

如果计数到达零,则该方法返回 true 值。 

	public class MyService {
	
	        public CountDownLatch count = new CountDownLatch(1);
	
	        public void testMethod() {
	
	        try {
	
	        System.out.println(Thread.currentThread().getName() + "准备" + System.currentTimeMillis());
	        count.await(3, TimeUnit.SECONDS);
	        System.out.println(Thread.currentThread().getName() + "结束" + System.currentTimeMillis());
	
	        } catch (Exception e) {
	                e.printStackTrace();
	        }
	
	        }
	
	}
	public class MyThread extends Thread {
	
	        private MyService service;
	
	        public MyThread(MyService service) {
	                this.service = service;
	        }
	
	        @Override
	        public void run() {
	                service.testMethod();
	        }
	
	}
	
	public class Run {
	
	        public static void main(String[] args) {
	
	        MyService service = new MyService();
	        MyThread[] tArr = new MyThread[3];
	        for (int i = 0; i < tArr.length; i++) {
	                tArr[i] = new MyThread(service);
	        }
	
	        for (int i = 0; i < tArr.length; i++) {
	                tArr[i].start();
	        }
	
	        }
	
	}
	
	输出结果:
	        Thread-0准备1496366982943
	        Thread-2准备1496366982943
	        Thread-1准备1496366982943
	        Thread-1结束1496366985954
	        Thread-2结束1496366985954
	        Thread-0结束1496366985954
	        
从代码中可以看出,没有调用countDown () 方法,超时后,程序自动往下执行


 

 

 

方法getCount()的使用

该方法作用是:获取当前计数器的值。

public class Run {

	public static void main(String[] args) {

	CountDownLatch down = new CountDownLatch(3);

	System.out.println("1-- " + down.getCount());
	down.countDown();
	System.out.println("2-- " + down.getCount());
	down.countDown();
	System.out.println("3-- " + down.getCount());
	down.countDown();
	System.out.println("4-- " + down.getCount());
	down.countDown();
	System.out.println("5-- " + down.getCount());
	}

}
输出结果:
	1-- 3
	2-- 2
	3-- 1
	4-- 0
	5-- 0
	
从结果看出,当计数器为 0 时,再次调用 countDown() ,计数器数量也不会再下降。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值