java面试题-说说你对CountDownLatch的理解
引言
在多线程编程中,实现线程之间的协调和通信是至关重要的。CountDownLatch是Java中提供的一个强大工具,用于协调多个线程的执行。本教程将深入探讨CountDownLatch的概念、用法以及示例代码,旨在帮助初学者更好地理解和运用这一同步工具。
1. CountDownLatch的概念
CountDownLatch是一个同步工具类,主要用于协调多个线程之间的同步。它并不是用来实现互斥访问的,而是用于线程之间的通信。其核心思想是通过一个计数器来实现线程间的等待和通知。
CountDownLatch的作用是允许一个或多个线程等待其他线程完成操作,然后再继续执行。
2. CountDownLatch的基本用法
CountDownLatch典型用法包括:
- 主线程等待其他线程执行完成再执行;
- 实现多个线程开始执行任务的最大并行性。
示例1:主线程等待子线程执行完成再执行
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CountdownLatchTest1 {
public static void main(String[] args) {
ExecutorService service = Executors.newFixedThreadPool(3);
final CountDownLatch latch = new CountDownLatch(3);
for (int i = 0; i < 3; i++) {
Runnable runnable = () -> {
try {
System.out.println("子线程" + Thread.currentThread().getName() + "开始执行");
Thread.sleep((long) (Math.random() * 10000));
System.out.println("子线程" + Thread.currentThread().getName() + "执行完成");
latch.countDown(); // 当前线程调用此方法,则计数减一
} catch (InterruptedException e) {
e.printStackTrace();
}
};
service.execute(runnable);
}
try {
System.out.println("主线程" + Thread.currentThread().getName() + "等待子线程执行完成...");
latch.await(); // 阻塞当前线程,直到计数器的值为0
System.out.println("主线程" + Thread.currentThread().getName() + "开始执行...");
} catch (InterruptedException e) {
e.printStackTrace();
}
service.shutdown();
}
}
解释示例1:
- 主线程创建了一个固定大小的线程池,初始化CountDownLatch计数器为3。
- 子线程在执行时,会调用
latch.countDown()
将计数器减一。 - 主线程通过
latch.await()
等待计数器为0,即等待所有子线程执行完成。 - 一旦计数器为0,主线程继续执行。
示例2:百米赛跑
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CountdownLatchTest2 {
public static void main(String[] args) {
ExecutorService service = Executors.newCachedThreadPool();
final CountDownLatch cdOrder = new CountDownLatch(1);
final CountDownLatch cdAnswer = new CountDownLatch(4);
for (int i = 0; i < 4; i++) {
Runnable runnable = () -> {
try {
System.out.println("选手" + Thread.currentThread().getName() + "正在等待裁判发布口令");
cdOrder.await();
System.out.println("选手" + Thread.currentThread().getName() + "已接受裁判口令");
Thread.sleep((long) (Math.random() * 10000));
System.out.println("选手" + Thread.currentThread().getName() + "到达终点");
cdAnswer.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
};
service.execute(runnable);
}
try {
Thread.sleep((long) (Math.random() * 10000));
System.out.println("裁判" + Thread.currentThread().getName() + "即将发布口令");
cdOrder.countDown();
System.out.println("裁判" + Thread.currentThread().getName() + "已发送口令,正在等待所有选手到达终点");
cdAnswer.await();
System.out.println("所有选手都到达终点");
System.out.println("裁判" + Thread.currentThread().getName() + "汇总成绩排名");
} catch (InterruptedException e) {
e.printStackTrace();
}
service.shutdown();
}
}
解释示例2:
- 选手线程等待裁判口令,裁判通过
cdOrder.countDown()
发布口令。 - 选手线程通过
cdAnswer.await()
等待所有选手到达终点,裁判通过cdAnswer.countDown()
通知所有选手到达。 - 裁判线程在所有选手到达后进行汇总。
3. CountDownLatch的不足
CountDownLatch是一次性的,计数器的值只能在构造方法中初始化一次,之后没有任何机制再次对其设置值。当CountDownLatch使用完毕后,它不能再次被使用。
4. CountDownLatch使用说明
方法说明
public void countDown()
递减锁存器的计数,如果计数到达零,则释放所有等待的线程。如果当前计数大于零,则将计数减少。
public boolean await(long timeout, TimeUnit unit) throws InterruptedException
使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断或超出了指定的等待时间。如果当前计数为零,则此方法立刻返回true值。
如果当前计数大于零,则出于线程调度目的,将禁用当前线程,且在发生以下三种情况之一前,该线程将一直处于休眠状态:
- 由于调用countDown()方法,计数到达零;
- 或者其他某个线程中断当前线程;
- 或者已超出指定的等待时间。
如果计数到达零
,则该方法返回true值。
如果当前线程:
- 在进入此方法时已经设置了该线程的中断状态;
- 或者在等待时被中断,
则抛出InterruptedException
,并且清除当前线程的已中断状态。
如果超出了指定的等待时间,则返回值为false。如果该时间小于等于零,则该方法根本不会等待。
参数
timeout
: 要等待的最长时间。unit
: timeout参数的时间单位。
返回值
- 如果计数到达零,则返回true;如果在计数到达零之前超过了等待时间,则返回false。
异常
InterruptedException
: 如果当前线程在等待时被中断。
5. 总结
CountDownLatch是一个强大的多线程同步工具,通过它可以实现线程之间的协调和通信。本教程通过详细的示例代码演示了CountDownLatch的基本用法,包括主线程等待子线程执行完成再执行和百米赛跑的场景。了解CountDownLatch的不足和使用说明,帮助初学者更好地理解和运用这一同步工具。
- 本文标签: Java 面试题
- 本文链接: https://www.jietongc.com/article/72
- 版权声明: 本文由大熊科技原创发布,转载请遵循《署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)》许可协议授权