CountDownLatch中count down是倒数的意思,latch则是门闩的含义。整体含义可以理解为倒数的门栓。
CountDownLatch的作用也是如此,在构造CountDownLatch(int count):的时候需要传入一个整数count,在这个整数“倒数”到0之前,主线程需要等待在门口,而这个“倒数”过程则是由各个执行线程驱动的,每个线程执行完一个任务“倒数”一次。
总结来说,CountDownLatch是Java中一个多线程工具类,用于控制线程的顺序,可以让主线程等待其他线程完成任务之后再继续执行,或者让多个线程之间相互等待。
int numberOfTasks = 5;
CountDownLatch latch = new CountDownLatch(numberOfTasks);
latch.await();
此方法返回一个布尔值,表示在等待时间内计数器是否变为0。
latch.await(5, TimeUnit.SECONDS);
这里需要注意的是,await()方法并没有规定只能有一个线程执行该方法,如果多个线程同时执行await()方法,那么这几个线程都将处于等待状态,并且以共享模式享有同一个锁。
latch.countDown();
这里需要注意的是,countDown()方法并没有规定一个线程只能调用一次,当同一个线程调用多次countDown()方法时,每次都会使计数器减一;
long remainingCount = latch.getCount();
在这个例子中,创建了5个线程,并让每个线程睡眠1秒钟,表示完成一个任务。在每个线程完成任务后,调用了countDown()方法,计数器减1。
在主线程中,调用了await()方法,等待所有线程完成任务。当所有线程的计数器都减为0时,主线程才会继续执行,输出"All tasks done"。
import java.util.concurrent.CountDownLatch;
public class CountDownLatchExample {
public static void main(String[] args) throws InterruptedException {
int n = 5; // 等待5个线程完成任务
CountDownLatch countDownLatch = new CountDownLatch(n);
for (int i = 0; i < n; i++) {
Thread t = new Thread(() -> {
try {
System.out.println(Thread.currentThread().getName() + " is working");
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + " done");
countDownLatch.countDown(); // 计数器减1
} catch (InterruptedException e) {
e.printStackTrace();
}
});
t.start();
}
countDownLatch.await(); // 等待其他线程完成任务
System.out.println("All tasks done");
}
}
输出结果:
Thread-0 is working
Thread-1 is working
Thread-2 is working
Thread-3 is working
Thread-4 is working
Thread-2 done
Thread-1 done
Thread-0 done
Thread-4 done
Thread-3 done
All tasks done
package base.threadabout.multhread.countdownlatch;
import java.util.concurrent.*;
/**
* CountDownLatch常用方法:await(),await(long,TimeUnit),countDown()
* await()会使线程休眠,直到countDownLatch的值递减到0,才会重新就绪
* await(long, TimeUnit) 休眠,直到countDownLatch的值递减到0或休眠时间结束
* 大概作用:等所有线程&某些线程都执行完了,再统一执行某个具体功能
*/
public class MainLoadingService {
public static void main(String[] args) throws InterruptedException {
CountDownLatch cdl = new CountDownLatch(5);
ExecutorService pool = Executors.newFixedThreadPool(5);
for (int i = 0; i < 5; i++) {
Loading runnable = new Loading(cdl);
pool.execute(runnable);
}
// 线程全部跑完的标志
System.out.println("等待子线程加载组件...");
cdl.await();
System.out.println("所有组件加载完毕,继续执行...");
pool.shutdown();
}
}
class Loading implements Runnable {
private CountDownLatch countDownLatch;
public Loading(CountDownLatch countDownLatch) {
this.countDownLatch = countDownLatch;
}
@Override
public void run() {
// 处理业务
String name = Thread.currentThread().getName();
System.out.println("子线程:" + name + "正在加载组件...");
// 业务处理完毕,countDownLatch-1
countDownLatch.countDown();
}
}
结果:
等待子线程加载组件...
子线程:pool-1-thread-1正在加载组件...
子线程:pool-1-thread-2正在加载组件...
子线程:pool-1-thread-3正在加载组件...
子线程:pool-1-thread-4正在加载组件...
子线程:pool-1-thread-5正在加载组件...
所有组件加载完毕,关闭线程池pool...
主线程定义new CountDownLatch(1)。每个子线程先执行await(),进入等待。等待所有子线程都开启,主线程执行countDown(),能确保所有子线程同时开始处理任务。
类似于赛跑,子线程是运动员,await是运动员的预备阶段,主线程是裁判,countDown是裁判的发令枪。枪响运动员才能跑。
package base.threadabout.multhread.countdownlatch;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class RaceGame {
public static void main(String[] args) throws InterruptedException {
ExecutorService pool = Executors.newFixedThreadPool(5);
CountDownLatch countDownLatch = new CountDownLatch(1);
for (int i = 0; i < 5; i++) {
Player player = new Player(i, countDownLatch);
pool.execute(player);
}
Thread.sleep(1000);
System.out.println("所有选手各就位.....GO!");
countDownLatch.countDown();
pool.shutdown();
}
static class Player implements Runnable{
private int id;
private CountDownLatch countDownLatch;
public Player(int id, CountDownLatch countDownLatch) {
this.id = id;
this.countDownLatch = countDownLatch;
}
@Override
public void run() {
System.out.println("参赛选手[" + id +"]号,准备就绪...");
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("参赛选手[" + id +"]号,到达终点...");
}
}
}
结果:
参赛选手[0]号,准备就绪...
参赛选手[1]号,准备就绪...
参赛选手[2]号,准备就绪...
参赛选手[3]号,准备就绪...
参赛选手[4]号,准备就绪...
所有选手各就位.....GO!
参赛选手[1]号,到达终点...
参赛选手[3]号,到达终点...
参赛选手[0]号,到达终点...
参赛选手[2]号,到达终点...
参赛选手[4]号,到达终点...
下面的示例展示了一个简单的网站爬虫,它使用 CountDownLatch 在主线程中等待其他爬虫线程完成任务。在这个例子中,我们要爬取一组网站的内容,在主线程中等待所有爬虫任务完成。
首先,我们创建一个 URLs 列表,包含多个网站 URL。
然后,我们使用 CountDownLatch 实例 latch 来跟踪待完成的爬虫任务数量。
接着,我们遍历 URL 列表,为每个 URL 创建一个新的 Crawler 线程。Crawler 类实现了 Runnable 接口,用于读取指定 URL 的网页内容。在完成任务后,它调用 latch.countDown() 方法减少计数值。
最后,在主线程中,我们调用 latch.await() 方法等待所有爬虫线程完成任务。当所有任务完成时,打印一条消息表示爬虫任务已完成。
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
public class WebCrawler {
private static class Crawler implements Runnable {
private final String url;
private final CountDownLatch latch;
public Crawler(String url, CountDownLatch latch) {
this.url = url;
this.latch = latch;
}
@Override
public void run() {
try {
URL urlObject = new URL(url);
BufferedReader in = new BufferedReader(new InputStreamReader(urlObject.openStream()));
String inputLine;
StringBuilder content = new StringBuilder();
while ((inputLine = in.readLine()) != null) {
content.append(inputLine);
content.append("\n");
}
in.close();
System.out.println("爬取 " + url + " 成功, 内容大小: " + content.length() + " 字符");
} catch (Exception e) {
System.err.println("爬取 " + url + " 失败, 原因: " + e.getMessage());
} finally {
latch.countDown();
}
}
}
public static void main(String[] args) throws InterruptedException {
List<String> urls = new ArrayList<>();
urls.add("https://github.com/");
urls.add("https://stackoverflow.com/");
urls.add("https://www.zhihu.com/");
urls.add("https://www.reddit.com/");
urls.add("https://www.linkedin.com/");
CountDownLatch latch = new CountDownLatch(urls.size());
System.out.println("开始爬虫任务...");
for (String url : urls) {
new Thread(new Crawler(url, latch)).start();
}
latch.await();
System.out.println("所有爬虫任务都已完成!");
}
}
运行结果
开始爬虫任务...
爬取 https://www.zhihu.com/ 成功, 内容大小: 37783 字符
爬取 https://github.com/ 成功, 内容大小: 227576 字符
爬取 https://stackoverflow.com/ 成功, 内容大小: 171290 字符
爬取 https://www.linkedin.com/ 成功, 内容大小: 12603 字符
爬取 https://www.reddit.com/ 失败, 原因: Read timed out
所有爬虫任务都已完成!
在这个例子中,我们将模拟一个简单的赛车游戏,
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
public class CountDownLatchAdvancedDemo {
public static void main(String[] args) throws InterruptedException {
int numberOfRacers = 5;
CountDownLatch startSignal = new CountDownLatch(1);
CountDownLatch finishSignal = new CountDownLatch(numberOfRacers);
// 创建赛车线程
for (int i = 0; i < numberOfRacers; i++) {
//这里虽然start,但是由于前面new了startSignal,并且实现类中await的影响会等待
new Thread(new Racer(startSignal, finishSignal)).start();
}
// 模拟倒计时
System.out.println("倒计时开始...");
for (int i = 3; i > 0; i--) {
System.out.println("倒计时: " + i);
TimeUnit.SECONDS.sleep(1);
}
System.out.println("比赛开始!");
startSignal.countDown(); // 启动信号
// 等待所有赛车完成比赛
finishSignal.await();
System.out.println("所有赛车都完成了比赛!");
}
static class Racer implements Runnable {
private CountDownLatch startSignal;
private CountDownLatch finishSignal;
public Racer(CountDownLatch startSignal, CountDownLatch finishSignal) {
this.startSignal = startSignal;
this.finishSignal = finishSignal;
}
@Override
public void run() {
try {
// 等待开始信号
startSignal.await();
// 正在比赛
System.out.println(Thread.currentThread().getName() + " 开始比赛...");
Thread.sleep((long) (Math.random() * 10000));
System.out.println(Thread.currentThread().getName() + " 完成比赛!");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 完成比赛后,递减完成信号计数
finishSignal.countDown();
}
}
}
}
在这个例子中,我们创建了两个 CountDownLatch:
当倒计时结束时,调用 startSignal.countDown(),开始信号变为0,并表示比赛开始。
每个线程在模拟赛车完成比赛后,调用 finishSignal.countDown() 减少完成信号计数。
主线程使用 finishSignal.await() 等待所有赛车线程都完成比赛。当计数值变为 0 时,主线程将打印一条消息表示所有赛车都完成了比赛。
运行结果:
倒计时开始...
倒计时: 3
倒计时: 2
倒计时: 1
比赛开始!
Thread-4 开始比赛...
Thread-2 开始比赛...
Thread-0 开始比赛...
Thread-1 开始比赛...
Thread-3 开始比赛...
Thread-4 完成比赛!
Thread-1 完成比赛!
Thread-0 完成比赛!
Thread-2 完成比赛!
Thread-3 完成比赛!
所有赛车都完成了比赛!