Java Semaphore方法异常:生产者消费者模型死锁问题求助
生产者消费者模型中Semaphore引发的死锁问题排查
问题描述
刚接触生产者消费者问题和Semaphore,代码出现死锁:生产者向队列添加元素后,再次获取许可时陷入阻塞,此时消费者线程未运行。代码如下:
public class UsingSemaphore { volatile static boolean check = true; public static void main(String args[]) throws InterruptedException { Semaphore semCon = new Semaphore(0); Semaphore semProd = new Semaphore(1); Queue<Integer> q = new LinkedList<>(); // Producer lambda Runnable producer = () -> { while (check) { try { semProd.acquire(); } catch (InterruptedException e) { e.printStackTrace(); } try { Random rand = new Random(); q.add(rand.nextInt(10)); }finally { semCon.release(); } } }; //Consumer lambda Runnable consumer = () -> { while (check) { try { semCon.acquire(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } try { System.out.println("Consumer item " + q.remove()); }finally { semProd.release(); } } }; ThreadGroup pg = new ThreadGroup("PG"); ThreadGroup cg = new ThreadGroup("CG"); Thread p1 = new Thread(pg, producer, "p1"); Thread c1 = new Thread(cg, consumer, "c1"); p1.start(); c1.start(); Thread.sleep(10); check = false; } }
死锁原因分析
死锁的核心是线程终止时机不合理,导致信号量许可未被正确释放:
- 生产者线程启动后,首先获取
semProd许可(许可数从1→0),生产元素后释放semCon(许可数从0→1)。 - 生产者再次进入循环(此时
check仍为true),尝试再次获取semProd许可,但此时许可数为0,进入阻塞状态。 - 主线程仅等待10ms就将
check设为false,消费者线程刚启动进入循环时,发现check为false,直接退出循环,从未执行semCon.acquire()和后续的semProd.release()操作。 - 生产者线程永远阻塞在
semProd.acquire()调用上,没有线程会再释放semProd许可,最终形成死锁。
另外还有一个潜在问题:LinkedList并非线程安全队列,多线程直接操作会引发并发修改异常或数据不一致问题。
修复方案
1. 调整线程终止逻辑,确保信号量正确释放
将check的判断移至信号量获取之后,保证线程即使收到终止信号,也能完成当前的生产/消费流程,释放对应的信号量:
public class UsingSemaphore { volatile static boolean check = true; public static void main(String args[]) throws InterruptedException { Semaphore semCon = new Semaphore(0); Semaphore semProd = new Semaphore(1); // 使用线程安全的ConcurrentLinkedQueue替代LinkedList Queue<Integer> q = new ConcurrentLinkedQueue<>(); // Producer lambda Runnable producer = () -> { while (true) { try { semProd.acquire(); // 获取许可后再检查终止信号 if (!check) { semProd.release(); // 释放许可,避免后续阻塞 break; } Random rand = new Random(); q.add(rand.nextInt(10)); } catch (InterruptedException e) { e.printStackTrace(); Thread.currentThread().interrupt(); break; } finally { semCon.release(); } } }; // Consumer lambda Runnable consumer = () -> { while (true) { try { semCon.acquire(); // 获取许可后再检查终止信号 if (!check) { semCon.release(); // 释放许可,避免后续阻塞 break; } System.out.println("Consumer item " + q.remove()); } catch (InterruptedException e) { e.printStackTrace(); Thread.currentThread().interrupt(); break; } finally { semProd.release(); } } }; ThreadGroup pg = new ThreadGroup("PG"); ThreadGroup cg = new ThreadGroup("CG"); Thread p1 = new Thread(pg, producer, "p1"); Thread c1 = new Thread(cg, consumer, "c1"); p1.start(); c1.start(); // 适当延长等待时间,让线程完成至少一轮生产消费 Thread.sleep(100); check = false; // 等待线程终止 p1.join(); c1.join(); } }
2. 关键修改点说明
- 将
while(check)改为while(true),在获取信号量后再判断终止条件,确保每次获取许可后都能完成对应操作并释放信号量。 - 使用
ConcurrentLinkedQueue替代LinkedList,保证队列操作的线程安全性。 - 主线程调用
join()等待生产者和消费者线程完全终止,避免程序提前退出。
额外提示
- Semaphore的核心是维护许可计数,必须严格保证
acquire()和release()的配对调用,否则会导致许可泄漏或死锁。 - 多线程环境下,所有共享资源(如队列)必须保证线程安全,避免并发问题。
内容的提问来源于stack exchange,提问作者Chetan Sharma 417
相关产品推荐
相关产品推荐

