【算法】多线程执行顺序控制(方法详解易懂版)5.27
问题要求:
有三个线程 A、B、C,分别调用 first()
、second()
、third()
,但必须保证:
first()
先执行,- 然后
second()
, - 最后
third()
。 - 但由于线程是并发执行的,操作系统可能会随机调度它们的顺序,所以需要强制让它们按顺序执行。
方法 1:
用 volatile
变量(最简单)
思路:
- 设置一个变量
flag
,用来标记当前应该执行哪个方法。 first()
执行完后,修改flag
,告诉second()
可以运行了。second()
执行完后,修改flag
,告诉third()
可以运行了。
代码:
public class Foo {
private volatile int flag = 1;// 1表示first可以执行,2表示second,3表示third public void first() { System.out.print("first"); flag = 2; // 告诉second可以执行了 } public void second() throws InterruptedException{ while (flag != 2) { // 如果flag不是2,就循环等待 } System.out.print("second"); flag = 3; // 告诉third可以执行了 } public void third() throws InterruptedException { while (flag != 3) { // 如果flag不是3,就循环等待 } System.out.print("third"); }}
解释:
volatile
保证flag
的修改对所有线程可见(避免缓存问题)。second()
和third()
会不断检查flag
,直到flag
变成它们需要的值才执行。
缺点:
while
循环会占用 CPU(忙等待),不太高效。
方法 2:
用 synchronized
+ wait/notify
(更高效)
思路:
- 用
synchronized
加锁,保证同一时间只有一个线程能进入关键代码。 wait()
让线程等待,notifyAll()
唤醒其他线程。
代码:
public class Foo {
private boolean firstDone = false;
private boolean secondDone = false;
private final Object lock = new Object(); // 锁对象 public void first() {
synchronized (lock) {
System.out.print("first"); firstDone = true; lock.notifyAll(); // 唤醒所有等待的线程 } } public void second() throws InterruptedException {
synchronized (lock) { while (!firstDone) { lock.wait(); // 如果first没执行完,就等待 } System.out.print("second"); secondDone = true; lock.notifyAll(); // 唤醒third } } public void third() throws InterruptedException { synchronized (lock) { while (!secondDone) { lock.wait(); // 如果second没执行完,就等待 } System.out.print("third"); } }}
解释:
synchronized
保证同一时间只有一个线程能进入lock
保护的代码块。wait()
会让线程释放锁并等待,直到被notifyAll()
唤醒。first()
执行完后,notifyAll()
会唤醒second()
,second()
执行完后唤醒third()
优点:
- 比
volatile
更高效,因为wait()
不会占用 CPU。
方法 3:
用 CountDownLatch
(推荐)
思路:
CountDownLatch
是一个计数器,初始值为 1。await()
会阻塞线程,直到计数器变成 0。-countDown()
会让计数器减 1。
代码:
import java.util.concurrent.CountDownLatch;
public class Foo {
private CountDownLatch latch1 = new CountDownLatch(1);
// first -> second
private CountDownLatch latch2 = new CountDownLatch(1);
// second -> third
public void first() {
System.out.print("first");
latch1.countDown(); // 让second可以执行
}
public void second() throws InterruptedException {
latch1.await(); // 等待first执行完
System.out.print("second");
latch2.countDown(); // 让third可以执行
}
public void third() throws InterruptedException {
latch2.await(); // 等待second执行完
System.out.print("third"); }
}
解释:
latch1
控制first()
->second()
:first()
执行完后,latch1.countDown()
,latch1
变成 0,second()
的await()
就会放行。latch2
控制second()
->third()
:second()
执行完后,latch2.countDown()
,latch2
变成 0,third()
的await()
就会放行。
优点:
- 代码简洁,适合这种固定顺序的线程控制。
方法 4:
用 Semaphore
(信号量)
思路:
Semaphore
可以控制同时访问的线程数量。- 初始值为 0 的信号量,
acquire()
会阻塞,直到release()
被调用。
代码:
import java.util.concurrent.Semaphore;
public class Foo {
private Semaphore sem1 = new Semaphore(0);
// first -> second
private Semaphore sem2 = new Semaphore(0);
// second -> third
public void first() {
System.out.print("first");
sem1.release(); // 让second可以执行
}
public void second() throws InterruptedException {
sem1.acquire(); // 等待first执行完
System.out.print("second");
sem2.release(); // 让third可以执行
}
public void third() throws InterruptedException {
sem2.acquire();
// 等待second执行完
System.out.print("third"); }}
解释:
sem1
控制first()
->second()
:first()
执行完后sem1.release()
,second()
的sem1.acquire()
就会放行。sem2
控制second()
->third()
: -second()
执行完后sem2.release()
,third()
的sem2.acquire()
就会放行。
优点:
- 灵活,适合更复杂的线程控制。
推荐:
- 如果是面试或简单场景,用
CountDownLatch
或volatile
。 - 如果需要更灵活的控制,用
Semaphore
或synchronized
。