CountDownLatch简介
以田径运动跑步为例,起跑前所有运动员等待裁判发枪声为准开始比赛、终点处计时裁判则需要等待所有运动员到达终点中宣布本轮次比赛结束,典型的多个线程等待一个线程、一个线程等待多个线程的场景,java.util.concurent包下的CountDownLatch就非常适合实现这种场景
CountDownLatch主要通过new CountdownLatch(n)的方式构建,调用await()方法的线程会进行挂起阻塞,通过countDown()方法进行计数器减一,直到n减为零为止,之前等待的线程就会恢复运行
CountDownLatch使用demo
以"起跑前所有运动员等待裁判发枪声准备起跑"的场景为例,模拟"多个线程等待一个线程":
/**
* @desc:模拟多个线程等待单个线程:所有运动员等待裁判发出枪声
**/
public class CountDownLatchTest1 {
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(7);
CountDownLatch latch = new CountDownLatch(1);
for (int i = 1; i < 8; i++) {
int j = i;
executorService.submit(() -> {
try {
//所有运动员都得等待裁判发出枪声才能开始,所以先"挂起"
latch.await();
Thread.sleep((long) new Random().nextInt(1) + 1);
System.out.println("运动员" + j + "起跑");
} catch (InterruptedException e) {
}
});
}
System.out.println("裁判发出枪响,比赛开始……");
latch.countDown();
executorService.shutdown();
}
}
//输出结果:
裁判发出枪响,比赛开始……
运动员1起跑
运动员3起跑
运动员4起跑
运动员5起跑
运动员2起跑
运动员6起跑
运动员7起跑
以"终点处计时裁判则需要等待所有运动员到达终点中宣布本轮次比赛结束"的场景为例,模拟"单个线程等待多个线程":
/**
* @desc:模拟单个线程等待多个线程:终点的裁判等待所有运动员都达到终点后宣布本批次结束
**/
public class CountDownLatchTest2 {
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(10);
CountDownLatch latch = new CountDownLatch(7);
for (int i = 1; i < 8; i++) {
int j = i;
executorService.submit(() -> {
try {
Thread.sleep((long) new Random().nextInt(1) + 1);
System.out.println("运动员" + j + "到达");
} catch (InterruptedException e) {
} finally {
latch.countDown();
}
});
}
latch.await();
System.out.println("所有运动员到达,裁判宣布本轮比赛结束!");
executorService.shutdown();
}
}
//输出结果:
运动员2到达
运动员1到达
运动员3到达
运动员4到达
运动员7到达
运动员5到达
运动员6到达
所有运动员到达,裁判宣布本轮比赛结束!
项目真实实例:
private List<UrlDto> getZipFileSystemUrls(List<FileDto> allFileDto) throws Exception {
List<UrlDto> allUrlDtos = Lists.newArrayList();
int threadCount = (int) allFileDto.stream().flatMap(k -> k.getFileList().stream()).filter(Objects::nonNull).count();
CountDownLatch latch = new CountDownLatch(threadCount);
Map<String, List<List<File>>> fileMap = allFileDto.stream().collect(Collectors.groupingBy(k -> k.getDataType(), Collectors.mapping(m -> m.getFileList(), Collectors.toList())));
for (Map.Entry<String, List<List<File>>> entry : fileMap.entrySet()) {
String dataType = entry.getKey();
List<File> zipfiles = entry.getValue().stream().flatMap(k -> k.stream()).filter(Objects::nonNull).collect(Collectors.toList());
for (File zipFile : zipfiles) {
threadPoolTaskExecutor.execute(() -> {
BufferedInputStream bis = null;
try {
bis = new BufferedInputStream(new FileInputStream(zipFile));
byte[] fileBytes = new byte[bis.available()];
bis.read(fileBytes);
//上传到文件系统-->url
String fileUrl = com.smy.fsp.client.FileUtil.uploadBuilder().fileName(zipFile.getName()).fileByte(fileBytes).sceneType("hiveZip").upload();
UrlDto urlDto = UrlDto.builder().dataType(dataType).urlList(Lists.newArrayList(fileUrl)).build();
allUrlDtos.add(urlDto);
} catch (Exception e) {
log.error("压缩文件上传文件系统出现异常e:{}", e);
} finally {
latch.countDown();
if (bis != null) {
try {
bis.close();
} catch (IOException e) {
log.error("BufferedInputStream关闭流出现异常e:{}", e);
}
}
}
});
}
}
latch.await();
Map<String, List<UrlDto>> groupUrlDto = allUrlDtos.stream().collect(Collectors.groupingBy(k -> k.getDataType()));
List<UrlDto> urlDtos = groupUrlDto.keySet().stream().map(k -> {
List<String> urls = Optional.ofNullable(groupUrlDto.get(k)).orElse(Lists.newArrayList()).stream().flatMap(m -> m.getUrlList().stream()).collect(Collectors.toList());
return UrlDto.builder().urlList(urls).dataType(k).build();
}).collect(Collectors.toList());
return urlDtos;
}
CyclicBarrer和CountDownLatc区别
1、CyclicBarrer的某个线程运行到屏障点后,线程立即停止运行,进入等待状态,直到所有的线程都达到了屏障点处,所有线程才继续运行;CountDownLatch则是当线程线程运行到某个点后,进行计数减1,该线程会继续运行;
2、CyclicBarrer可重复用(重置),CountDownLatch则不可以;
3、CyclicBarrer通过减计数方式,计数到达指定值时释放所有等待的线程,CountDownLatch通过加计数方式,计数为0时释放所有等待的线程;
4、CountDownLatch调用countDown()方法计数减一,调用await()方法只进行阻塞,对计数没任何影响,CyclicBarrer调用await()方法计数加1,若加1后的值不等于构造方法的值,则线程阻塞。