
本文详解如何通过 semaphore 控制两个线程严格交替执行,解决因串行调用导致的“无并发”问题,确保输出为连续的 "foobarfoobar...";核心在于正确启动独立线程并初始化信号量状态。
本文详解如何通过 semaphore 控制两个线程严格交替执行,解决因串行调用导致的“无并发”问题,确保输出为连续的 "foobarfoobar...";核心在于正确启动独立线程并初始化信号量状态。
在多线程协作场景中,常需控制多个线程按特定顺序交替执行——例如经典的 FooBar 交替打印问题:一个线程打印 "Foo",另一个打印 "Bar",最终输出严格为 "FooBarFooBar..."(共 5 组即 "FooBar" 重复 5 次)。初学者常误将两个任务顺序调用(如先 foo() 再 bar()),导致实际仍是单线程执行,完全丧失并发性与同步意义。
根本问题在于原代码的 main 方法中:
pt.foo(printFo); // 主线程阻塞执行完全部 foo pt.bar(printBa); // 然后才执行 bar → 输出为 "FooFooFooFooFooBarBarBarBarBar"
这并非两个线程协作,而是两个方法在同一线程中串行执行,Semaphore 完全未发挥作用。
✅ 正确做法是:
- 创建两个独立 Thread,分别运行 foo() 和 bar();
- 利用 Semaphore 的初始许可数实现初始执行权分配:
- f = new Semaphore(1):允许 foo 首先获取许可并执行;
- t = new Semaphore(0):bar 初始无法获取许可,必须等待 foo 释放;
- 每次 foo 执行完即 t.release(),唤醒 bar;bar 执行完即 f.release(),唤醒 foo,形成闭环。
以下是修正后的完整可运行代码:
import java.util.concurrent.Semaphore;
public class PrintThread {
private final int n = 5;
private final Semaphore fooPermit = new Semaphore(1); // 初始许可:foo 先行
private final Semaphore barPermit = new Semaphore(0); // 初始无许可:bar 等待
public void foo(Runnable printFoo) {
for (int i = 0; i < n; i++) {
try {
fooPermit.acquire(); // 等待轮到自己
printFoo.run(); // 打印 "Foo"
barPermit.release(); // 交棒给 bar
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException(e);
}
}
}
public void bar(Runnable printBar) throws InterruptedException {
for (int i = 0; i < n; i++) {
barPermit.acquire(); // 等待 foo 释放许可
printBar.run(); // 打印 "Bar"
fooPermit.release(); // 交棒给 foo
}
}
public static void main(String[] args) throws Exception {
PrintThread pt = new PrintThread();
Runnable printFoo = () -> System.out.print("Foo");
Runnable printBar = () -> System.out.print("Bar");
Thread t1 = new Thread(() -> pt.foo(printFoo));
Thread t2 = new Thread(() -> {
try {
pt.bar(printBar);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
t1.start();
t2.start();
// 等待两线程完成(更健壮的做法是使用 join)
t1.join();
t2.join();
System.out.println(); // 换行便于观察输出
}
}? 关键注意事项:
- ✅ 必须使用 Thread.start() 启动真正并发的线程,而非直接调用方法;
- ✅ join() 替代 Thread.sleep(1000):避免竞态(如线程未执行完就结束程序);
- ✅ Semaphore.acquire() 可能被中断,务必正确处理 InterruptedException(恢复中断状态或转为 unchecked 异常);
- ⚠️ 不要忽略 finally 块中的资源释放(本例无外部资源,但复杂场景需注意);
- ? 输出验证:运行后应稳定输出 FooBarFooBarFooBarFooBarFooBar(共 10 个单词,无换行、无空格)。
该模式是理解线程协调机制的经典范例——Semaphore 在此充当轻量级“令牌调度器”,比 wait/notify 更简洁,比 ReentrantLock + Condition 更聚焦于计数语义。掌握它,是构建可控并发流程的重要一步。










