最近准备面试刷题,遇到一个多线程问题:如何让 A、B、C 三个线程同时启动,却要按顺序先后执行(A -> B -> C)。
直觉上,“同时启动”意味着线程调度权交给操作系统,执行顺序理应不确定。那如何控制顺序?若让后一个线程等待前一个执行完毕,似乎又违背了“同时启动”的要求。
最初尝试 Thread.join()
:让 B 等待 A 执行完,C 等待 B 执行完,再按顺序启动它们。但很快发现问题——如果 B 和 C 的初始化逻辑依赖 A 对象,它们甚至无法在 A 启动前被创建。这本质上只是“顺序启动”,而非题目要求的“同时启动但顺序执行”。
理解关键点:
start()
仅通知系统线程就绪,run()
方法内的逻辑何时执行取决于 CPU 调度。- “启动”是状态,“执行”是动作,“等待”是一种阻塞状态。
- 核心在于控制
run()
方法中关键逻辑的执行顺序,而非启动顺序本身。
使用 CountDownLatch 实现: CountDownLatch
是一个类似发令枪的同步工具。其核心是一个计数器:初始化设定计数值,线程调用 await()
阻塞等待;其他线程完成任务后调用 countDown()
减 1;当计数器归零,所有等待线程被唤醒继续执行。
利用两个 CountDownLatch
:一个控制 B 等待 A 完成,一个控制 C 等待 B 完成。
public class ThreadOrder {
public static void main(String[] args) {
// 控制B等待A的信号
CountDownLatch latchBwaitsA = new CountDownLatch(1);
// 控制C等待B的信号
CountDownLatch latchCwaitsB = new CountDownLatch(1);
Thread a = new Thread(() -> {
System.out.println("A starts");
latchBwaitsA.countDown(); // A执行完毕,释放B线程的阻塞
});
Thread b = new Thread(() -> {
try {
latchBwaitsA.await(); // B等待A完成信号
System.out.println("B starts");
latchCwaitsB.countDown(); // B执行完毕,释放C线程的阻塞
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
Thread c = new Thread(() -> {
try {
latchCwaitsB.await(); // C等待B完成信号
System.out.println("C starts");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
// 真正的“同时启动”
a.start();
b.start();
c.start();
}
}
输出结果:
A starts
B starts
C starts
最关键的是,三个线程的 start()
方法几乎在同一时刻被调用,满足了“同时启动”的要求;而 run()
方法中关键逻辑的执行顺序(打印语句)则通过 CountDownLatch
实现了严格的 A -> B -> C 顺序,没有破坏“同时启动”的前提。