关于CountDownLatch

CountDownLatch是一个计数器,用于线程间的同步,常用于让一个线程等待其他线程完成任务。工作原理是线程每完成一部分工作就调用countDown(),当计数器归零时,await()阻塞的线程会被释放。与Join不同,CountDownLatch不依赖于线程终止,更加灵活。Join则需等待线程执行完毕才释放资源。在特定场景下,如部分任务完成后就可继续执行,CountDownLatch表现更优。

CountDownLatch 是什么

CountDownLatch这个类能够使一个线程等待其他线程完成各自的工作后再执行。例如,应用程序的主线程希望在负责启动框架服务的线程已经启动所有的框架服务之后再执行。

使用场景:
Zookeeper分布式锁,Jmeter模拟高并发等。

CountDownLatch 如何工作

CountDownLatch 是通过一个计数器来实现的,计数器的初始值为线程的数量。每当一个线程完成了自己的任务后调用 countDown(),计数器的值就会减1。当计数器值到达0时,它表示所有的线程已经完成了任务,被 await() 方法阻塞的线程才会被释放。

CountDownLatch API

CountDownLatch API 描述

使用示例

假设一条流水线上有三个工作者:worker0,worker1,worker2。有一个任务的完成需要他们三者协作完成,worker2可以开始这个任务的前提是 worker0 和 worker1 完成了他们的工作,而 worker0 和worker1 是可以并行他们各自的工作的。

接下来我们创建一个计数器为 2 的 CountDownLatch 实例 ,让Worker 持有这个 CountDownLatch 实例,当完成自己的工作后,调用countDownLatch. countDown() 方法将计数器减1。countDownLatch.await() 方法会一直阻塞直到计数器为0,主线程才会继续往下执行。

Worker 类如下:

package com.concurrent.test4;
 
import java.util.concurrent.CountDownLatch;
 
/**
 * 工作者类
 * @author ThinkPad
 *
 */
public class Worker extends Thread {
 
	//工作者名
        private String name;
	//工作时间
	private long time;
	
	private CountDownLatch countDownLatch;
	
	public Worker(String name, long time, CountDownLatch countDownLatch) {
		this.name = name;
		this.time = time;
		this.countDownLatch = countDownLatch;
	}
	
	@Override
	public void run() {
		try {
			System.out.println(name+"开始工作");
			Thread.sleep(time);
			System.out.println(name+"工作完成,耗费时间="+time);
			countDownLatch.countDown();
			System.out.println("countDownLatch.getCount()="+countDownLatch.getCount());
		} catch (InterruptedException e) {
			// TODO 自动生成的 catch 块
			e.printStackTrace();
		}	
	}
}

Test 类如下:

package com.concurrent.test4;
 
import java.util.concurrent.CountDownLatch;
 
 
public class Test {
 
	public static void main(String[] args) throws InterruptedException {
		CountDownLatch countDownLatch = new CountDownLatch(2);
		Worker worker0 = new Worker("worker0", (long) (Math.random()*2000+3000), countDownLatch);
		Worker worker1 = new Worker("worker1", (long) (Math.random()*2000+3000), countDownLatch);
		Worker worker2 = new Worker("worker2", (long) (Math.random()*2000+3000), countDownLatch);
		
		worker0.start();
		worker1.start();
		
		countDownLatch.await();
		System.out.println("准备工作就绪");
		worker2.start();		
	}
}

观察运行结果:

worker1开始工作
worker0开始工作
worker0工作完成,耗费时间=3174
countDownLatch.getCount()=1
worker1工作完成,耗费时间=3870
countDownLatch.getCount()=0
准备工作就绪
worker2开始工作
worker2工作完成,耗费时间=3992

与 Join 的区别

除了 CountDownLatch 外,我们还可以使用 Join 来模拟以上场景。

当在当前线程中调用某个线程 thread 的 join() 方法时,当前线程就会阻塞,直到 thread 执行完成,当前线程才可以继续往下执行。

补充下:join 的工作原理是,不停检查 thread 是否存活,如果存活则让当前线程永远 wait,直到 thread 线程终止,线程的 this.notifyAll() 就会被调用。

接下来使用 Join 来实现一下。

Worker 类如下:

package com.concurrent.test3;
 
/**
 * 工作者类
 * @author ThinkPad
 *
 */
public class Worker extends Thread {
 
	//工作者名
	private String name;
	//工作时间
	private long time;
	
	public Worker(String name, long time) {
		this.name = name;
		this.time = time;
	}
	
	@Override
	public void run() {
		// TODO 自动生成的方法存根
		try {
			System.out.println(name+"开始工作");
			Thread.sleep(time);
			System.out.println(name+"工作完成,耗费时间="+time);
		} catch (InterruptedException e) {
			// TODO 自动生成的 catch 块
			e.printStackTrace();
		}	
	}
}

Test 类如下:

package com.concurrent.test3;
 
 
public class Test {
 
	public static void main(String[] args) throws InterruptedException {
 
		Worker worker0 = new Worker("worker0", (long) (Math.random()*2000+3000));
		Worker worker1 = new Worker("worker1", (long) (Math.random()*2000+3000));
		Worker worker2 = new Worker("worker2", (long) (Math.random()*2000+3000));
		
		worker0.start();
		worker1.start();
		
		worker0.join();
		worker1.join();
		System.out.println("准备工作就绪");
		
		worker2.start();		
	}
}

观察运行结果:

worker1开始工作
worker0开始工作
worker1工作完成,耗费时间=3947
worker0工作完成,耗费时间=4738
准备工作就绪
worker2开始工作
worker2工作完成,耗费时间=4513

可以发现 Join 确实可以完成这个需求,那么它和 CountDownLatch 有哪些区别呢?

目前所知的 CountDownLatch 与 Join 的不同点:

Join 会不停检查 thread 是否存活,如果存活则让当前线程永远 wait,直到 thread 线程终止,线程的 this.notifyAll() 就会被调用。

CountDownLatch 通过计数器是否为 0 决定是否释放资源,并不需要线程终止才会去释放资源。

可见 CountDownLatch 比 Join 要灵活许多,我们使用另一种场景来说明这个问题。

假设 worker 的工作可以分为两个阶段,work2 只需要等待 work0和 work1 完成他们各自工作的第一个阶段之后就可以开始自己的工作了,而不是场景1中的必须等待 work0 和 work1 把他们的工作全部完成之后才能开始。

这种情况是 Join 无法实现的,而 CountDownLatch 却很轻松。我们可以在 worker0 和 worker1 完成第一阶段工作之后就 调用 countDown() 把计数器减 1 即可,这样 worker0 和 worker1 在完成第一阶段工作之后,worker2 就可以开始工作了。

Worker 类如下:

package com.concurrent.test5;
 
import java.util.concurrent.CountDownLatch;
 
/**
 * 工作者类
 * @author ThinkPad
 *
 */
public class Worker extends Thread {
 
	//工作者名
    private String name;
	//第一阶段工作时间
	private long time;
	
	private CountDownLatch countDownLatch;
	
	public Worker(String name, long time, CountDownLatch countDownLatch) {
		this.name = name;
		this.time = time;
		this.countDownLatch = countDownLatch;
	}
	
	@Override
	public void run() {
		try {
			System.out.println(name+"开始工作");
			Thread.sleep(time);
			System.out.println(name+"第一阶段工作完成");
			
			countDownLatch.countDown();
			
			Thread.sleep(2000); //这里就姑且假设第二阶段工作都是要2秒完成
			System.out.println(name+"第二阶段工作完成");
			System.out.println(name+"工作完成,耗费时间="+(time+2000));
			
		} catch (InterruptedException e) {
			e.printStackTrace();
		}	
	}
}

Test 类如下:

package com.concurrent.test5;
 
import java.util.concurrent.CountDownLatch;
 
 
public class Test {
 
	public static void main(String[] args) throws InterruptedException {
		CountDownLatch countDownLatch = new CountDownLatch(2);
		Worker worker0 = new Worker("worker0", (long) (Math.random()*2000+3000), countDownLatch);
		Worker worker1 = new Worker("worker1", (long) (Math.random()*2000+3000), countDownLatch);
		Worker worker2 = new Worker("worker2", (long) (Math.random()*2000+3000), countDownLatch);
		
		worker0.start();
		worker1.start();	
		countDownLatch.await();
		
		System.out.println("准备工作就绪");
		worker2.start();
		
	}
 
}

观察运行结果:

worker0开始工作
worker1开始工作
worker1第一阶段工作完成
worker0第一阶段工作完成
准备工作就绪
worker2开始工作
worker1第二阶段工作完成
worker1工作完成,耗费时间=5521
worker0第二阶段工作完成
worker0工作完成,耗费时间=6147
worker2第一阶段工作完成
worker2第二阶段工作完成
worker2工作完成,耗费时间=5384

如此,变可以看出 Join 的短板。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值