
本文详解如何使用 semaphore 精确控制两个线程交替执行(如打印 "foobar" 循环),指出原代码因串行调用导致逻辑失效,并提供可运行的多线程解决方案,涵盖信号量初始化、acquire/release 配对原则及线程启动关键细节。
本文详解如何使用 semaphore 精确控制两个线程交替执行(如打印 "foobar" 循环),指出原代码因串行调用导致逻辑失效,并提供可运行的多线程解决方案,涵盖信号量初始化、acquire/release 配对原则及线程启动关键细节。
在并发编程中,Semaphore 是实现线程协作与顺序控制的重要工具。但初学者常误将同步逻辑写成单线程串行调用,从而完全丧失并发语义——这正是原始代码的核心问题:pt.foo(...) 和 pt.bar(...) 在 main 方法中被顺序执行,而非并发启动,导致 Semaphore 的阻塞/唤醒机制从未真正生效。
要实现预期输出 FooBarFooBar...(共 5 对),必须满足:
- 两个线程并发启动,并严格遵循“Foo → Bar → Foo → Bar…”的交替节奏;
- foo() 线程初始持有许可(f = new Semaphore(1)),可立即执行;
- bar() 线程初始无许可(t = new Semaphore(0)),必须等待 foo() 释放后才能运行;
- 每次执行后,主动将许可“交棒”给对方线程。
以下是修正后的完整可运行代码:
本文档主要讲述的是Matlab语言的特点;Matlab具有用法简单、灵活、程式结构性强、延展性好等优点,已经逐渐成为科技计算、视图交互系统和程序中的首选语言工具。特别是它在线性代数、数理统计、自动控制、数字信号处理、动态系统仿真等方面表现突出,已经成为科研工作人员和工程技术人员进行科学研究和生产实践的有利武器。希望本文档会给有需要的朋友带来帮助;感兴趣的朋友可以过来看看
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(); // 等待获取 foo 许可
printFoo.run(); // 打印 "Foo"
barPermit.release(); // 释放 bar 许可,允许 bar 执行
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("Foo thread interrupted", e);
}
}
}
public void bar(Runnable printBar) throws InterruptedException {
for (int i = 0; i < n; i++) {
barPermit.acquire(); // 等待 bar 许可(由 foo 释放)
printBar.run(); // 打印 "Bar"
fooPermit.release(); // 释放 foo 许可,允许下一轮 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 显式创建并启动两个独立线程(t1.start() / t2.start()),而非直接调用方法;
- 异常处理规范化:foo() 中捕获 InterruptedException 并恢复中断状态(interrupt()),避免吞没中断信号;
- 主线程同步:用 t1.join() 和 t2.join() 替代 Thread.sleep(1000),确保主线程等待子线程自然结束,消除竞态与超时不确定性;
- 命名与可读性:将 f/t 改为语义清晰的 fooPermit/barPermit,强化设计意图。
⚠️ 注意事项:
- Semaphore 的 acquire() 是阻塞操作,若未配对 release(),极易引发死锁;务必保证每次 acquire() 后有且仅有一次对应 release();
- 不要在线程未启动前调用 join(),否则会立即返回;
- 若需扩展为 N 线程轮转,建议改用 CyclicBarrier 或 Phaser,Semaphore 在复杂场景下维护成本较高。
通过本例可见:信号量本身无错,问题在于线程生命周期管理。掌握“启动→协作→等待”三阶段,才能让 Semaphore 发挥其精准调度的真正价值。









