CountDownLatch是Java并发编程的常用类,是通过一个计数器来实现的,计数器的初始值是线程的数量。每当一个线程执行完毕后,计数器的值就-1,当计数器的值为0时,在闭锁上等待的线程就会被唤醒。
1、原理介绍
① 构造器中传入的count值是计数器的初始值,是闭锁需要等待的线程数量,这个值只能被设置一次,而且不能重新设置。
② await()方法会是调用此方法的线程阻塞,直到计数器的值减到0才被唤醒。
③ countDown()方法会使计数器值 -1,可在子线程执行完任务调用此方法,并且要尽量放在finally中,避免死锁。
2、场景1:让主线程等待,在多个子线程完成后再执行
2.1、说明
该使用场景是让主线程等待,在多个子线程完成后再执行,如数据详情页需要同时调用多个接口获取数据,并发请求获取到数据后、需要进行结果合并;或者多个数据操作完成后,需要数据检查。
public class Test1 {
public static void main(String[] args) throws Exception {
CountDownLatch count = new CountDownLatch(5);//子线程数量
for (int i = 1; i <= 5; i++) {
final int index = i;
new Thread(() -> {
System.out.println("子线程"+ index +"执行……");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
count.countDown();//计数值-1
}
}).start();
}
count.await();//主线程阻塞等待
System.out.println("所有子线程完成后,主线程执行!");
}
}
2.2、示例
有这样一种业务场景:需要请求多个外部API去获取数据,且多个API之间并无数据递进关系,此时可以采用并发请求的方式提高效率。
如在办理车辆业务平台中需要请求不同的API获取车辆信息和违章信息,采用同步方式请求耗时如下:
@RequestMapping("/test1")
public String test1() throws Exception {
String plateNumber = "粤XXXXXX";
String plateType = "02";
String engine = "000000";
String vin = "000000";
String idCardNo = "000000000000000000";
long start = System.currentTimeMillis();
//获取车辆信息
JSONObject vehicleInfo = vehicleInfoService.getVehicleInfo(plateNumber, plateType, engine, idCardNo);
System.out.println("vehicleInfo: " + vehicleInfo);
//获取违章信息
JSONObject illegalInfo = illegalInfoService.getIllegalInfo(plateNumber, plateType, engine, vin);
System.out.println("illegalInfo: " + illegalInfo);
long end = System.currentTimeMillis();
String s = "总耗时:" + (end - start);
System.out.println(s);
return s;
}
总耗时:2461
改成用CountDownLatch实现并发请求结果如下:
@RequestMapping("/test1")
public String test1() throws Exception {
String plateNumber = "粤XXXXXX";
String plateType = "02";
String engine = "000000";
String vin = "000000";
String idCardNo = "000000000000000000";
CountDownLatch count = new CountDownLatch(2);//子线程数量
long start = System.currentTimeMillis();
//获取车辆信息
new Thread(() -> {
try {
JSONObject vehicleInfo = vehicleInfoService.getVehicleInfo(plateNumber, plateType, engine, idCardNo);
System.out.println("vehicleInfo: " + vehicleInfo);
} catch (Exception e) {
e.printStackTrace();
} finally {
count.countDown();//计数值-1
}
}).start();
//获取违章信息
new Thread(() -> {
try {
JSONObject illegalInfo = illegalInfoService.getIllegalInfo(plateNumber, plateType, engine, vin);
System.out.println("illegalInfo: " + illegalInfo);
} catch (Exception e) {
e.printStackTrace();
} finally {
count.countDown();//计数值-1
}
}).start();
count.await();//主线程阻塞等待
long end = System.currentTimeMillis();
String s = "总耗时:" + (end - start);
System.out.println(s);
return s;
}
总耗时:1012
3、场景2:让多个子线程等待,在主线程完成后再执行
3.1、说明
该场景是让多个子线程等待,在主线程完成后,用countdown()将计数器减为0时,同时开始执行,类似发令枪,让多个运动员同时起跑,常用于模拟高并发场景。
public class Test2 {
public static void main(String[] args) throws InterruptedException {
CountDownLatch count = new CountDownLatch(1);//主线程数量
for (int i = 1; i <= 5; i++) {
final int index = i;
new Thread(() -> {
try {
count.await();//调用此方法的子线程会被阻塞在这,直到主线程计数值为0被唤醒
System.out.println("子线程"+index+"开始执行……");
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
System.out.println("主线程开始执行……");
TimeUnit.SECONDS.sleep(2);
System.out.println("主线程执行完成,唤醒所有子线程!");
count.countDown();//主线程计数值减到0,唤醒子线程
}
}
3.2、示例
作高并发测试工具,以下以测试SimpleDateFormat类线程不安全场景为例:
@Service
public class SdfService {
private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public void test1() throws Exception{
final String[] strArr = new String[] {"2021-02-09 10:24:00", "2021-02-10 20:48:00", "2021-02-11 12:24:00"};
int max = 2;
int min = 0;
Random random = new Random();
int s = random.nextInt(max)%(max-min+1) + min;
System.out.println(Thread.currentThread().getName()+ "\t" + sdf.parse(strArr[s]));
}
}
测试方法如下:
@RunWith(SpringRunner.class)
@SpringBootTest
public class TestCountDownLatch {
@Autowired
private SdfService sdfService;
@Test
public void test1() throws Exception{
CountDownLatch count = new CountDownLatch(1);
for (int i = 1; i <= 50; i++) {//50个线程并发
new Thread(() -> {
try {
count.await();
sdfService.test1();
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
TimeUnit.SECONDS.sleep(2);
System.out.println("所有子线程同时开始解析字符日期:");
count.countDown();
}
}
版权声明:本文为weixin_44360895原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。