
本文详解使用 semaphore 实现生产者-消费者模型时因共享标志位未同步、信号量配对失衡导致的典型死锁问题,并提供线程安全、可终止的完整解决方案。
本文详解使用 semaphore 实现生产者-消费者模型时因共享标志位未同步、信号量配对失衡导致的典型死锁问题,并提供线程安全、可终止的完整解决方案。
在使用 Semaphore 实现生产者-消费者模型时,初学者常误以为仅靠信号量的 acquire()/release() 就能保证线程协作的正确性,而忽略两个关键前提:共享状态的可见性与生命周期终止的协同性。您提供的代码中,volatile static boolean check = true 虽声明为 volatile,但其读写仍存在竞态风险——更严重的是,主线程将 check 设为 false 后,消费者线程可能已退出循环,而生产者却仍在等待 semProd 许可,导致永久阻塞(死锁)。
根本原因在于信号量的“许可流”被单向切断:
- 初始 semProd=1 允许生产者首次执行;
- 生产者添加元素后释放 semCon(+1),唤醒消费者;
- 消费者消费后释放 semProd(+1),本应允许下一轮生产;
- 但若此时 check == false 已被消费者读取并退出,semProd 的这次 release 就无人等待,而生产者在下一次循环开头立即调用 semProd.acquire() —— 此时若 semProd 许可数仍为 0(因消费者未再 acquire),便无限等待。
✅ 正确解法需同时满足三点:
- 使用 AtomicBoolean 替代 volatile boolean,确保 check 的读写原子性与可见性;
- 在 acquire 前检查终止条件,避免线程在已应停止时仍尝试获取许可;
- 确保信号量操作与业务逻辑严格配对,且退出前做最终清理(如必要)。
以下是修复后的完整示例:
立即学习“Java免费学习笔记(深入)”;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
public class SafeSemaphoreProducerConsumer {
private static final AtomicBoolean running = new AtomicBoolean(true);
public static void main(String[] args) throws InterruptedException {
Semaphore semCon = new Semaphore(0); // 初始无可用消费项
Semaphore semProd = new Semaphore(1); // 初始允许1次生产
Queue<Integer> queue = new LinkedList<>();
Runnable producer = () -> {
Random rand = new Random();
while (running.get()) {
try {
// ✅ 关键:先检查再 acquire,避免无效等待
if (!running.get()) break;
semProd.acquire(); // 等待生产许可
int item = rand.nextInt(100);
queue.add(item);
System.out.println("✓ Produced: " + item);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
} finally {
semCon.release(); // 通知消费者有新项
}
}
};
Runnable consumer = () -> {
while (running.get()) {
try {
if (!running.get()) break;
semCon.acquire(); // 等待消费项
Integer item = queue.poll();
System.out.println("← Consumed: " + item);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
} finally {
semProd.release(); // 归还生产许可
}
}
};
Thread p = new Thread(producer, "Producer");
Thread c = new Thread(consumer, "Consumer");
p.start();
c.start();
// 运行 1.5 秒后安全终止
Thread.sleep(1500);
running.set(false);
// 等待线程自然退出(避免强制 interrupt 导致信号量状态异常)
p.join(100);
c.join(100);
System.out.println("✓ Program terminated gracefully.");
}
}⚠️ 注意事项:
- 永不忽略 InterruptedException:捕获后应恢复中断状态(Thread.currentThread().interrupt()),而非静默吞掉;
- volatile 不足以保障复合操作原子性:running.get() 是安全的,但 if (running.get()) { ... } 中的条件判断与后续 acquire() 之间仍存在时间窗口,因此必须在 acquire() 前再次检查;
- 避免 ThreadGroup 和手动 Thread.sleep(10) 控制流程:它们不可靠且难以调试,应使用明确的生命周期控制(如 AtomicBoolean + join());
- LinkedList 非线程安全,但此处由信号量严格串行访问,无需额外同步——若需更高并发,可换用 ConcurrentLinkedQueue 并调整信号量逻辑。
总结:Semaphore 是强大的同步工具,但其正确性高度依赖开发者对“许可流”的精确建模。生产环境务必结合原子变量、清晰的退出协议与防御性检查,才能构建健壮、可终止的并发组件。


















