ReentrantLock调用signal()线程未唤醒 逻辑仅执行一次问题排查
问题根因
signal()方法本身没有失效,代码出现永久阻塞的核心原因是await()与signal()的执行顺序写反,再加上缺少轮次状态校验,最终导致两个线程全部进入永久等待状态。
具体问题点如下:
- 逻辑顺序完全颠倒:现有写法是先调用
await()挂起当前线程,再执行signal()唤醒目标线程。await()方法会直接阻塞当前执行流,一旦线程进入条件等待队列,await()后续的代码不会有任何执行机会,相当于唤醒逻辑永远不会被触发。 - 第一轮执行就会触发死锁,流程如下:
- 学生线程没有启动延迟,率先抢到锁执行完提问流程,调用
forStudent.await():此时学生线程自动释放锁、进入forStudent对应的等待队列挂起,后续的forJoGyo.signal()根本没有执行机会。 - 助教线程初始100ms的休眠结束后,成功抢到锁执行完自己的提问流程,调用
forJoGyo.await():助教线程同样自动释放锁、进入forJoGyo对应的等待队列挂起,后续的forStudent.signal()也无法执行。 - 此时两个工作线程全部处于等待状态,没有任何活跃线程持有锁、也没有线程能调用唤醒方法,程序自然会卡住不再继续执行。
- 学生线程没有启动延迟,率先抢到锁执行完提问流程,调用
- 缺少轮次控制标记:就算调整了唤醒顺序,没有状态位校验当前执行权限的话,一旦线程意外抢占到锁,会直接打破交替执行的规则,甚至再次触发阻塞。
修复方案
核心调整三点:一是将唤醒逻辑移到await()调用之前,保证先唤醒对方再挂起自己;二是增加轮次状态位,控制两个线程的执行顺序;三是将锁释放逻辑移到finally块,避免异常导致锁无法释放。
修正后的Professor类代码如下:
class Professor { private final ReentrantLock lock = new ReentrantLock(); private final Condition forJoGyo = lock.newCondition(); private final Condition forStudent = lock.newCondition(); // 轮次标记:true表示当前轮到学生提问,初始值为true让学生线程先执行 private boolean isStudentTurn = true; public void ask() { lock.lock(); try { String threadName = Thread.currentThread().getName(); // 不属于当前线程的轮次,直接进入等待 if (threadName.contains("assistant") && isStudentTurn) { forJoGyo.await(); } if (threadName.contains("student") && !isStudentTurn) { forStudent.await(); } // 执行提问业务逻辑 System.out.println(threadName + ": have a question. professor!"); for (int i = 0; i < 5; i++) { Thread.sleep(500); System.out.println("professor: explaining.." + i + " " + threadName + " listening.."); } System.out.println(threadName + ": thanks professor"); // 切换轮次,先唤醒对方线程,再挂起当前线程 if (threadName.contains("assistant")) { isStudentTurn = true; forStudent.signal(); forJoGyo.await(); } else if (threadName.contains("student")) { isStudentTurn = false; forJoGyo.signal(); forStudent.await(); } } catch (InterruptedException e) { // 恢复中断标记 Thread.currentThread().interrupt(); } finally { // 保证锁一定能被释放 lock.unlock(); } } }
关键注意事项
- 调用
Condition的await()、signal()方法时,必须保证当前线程已经持有对应的锁,否则会直接抛出IllegalMonitorStateException。 await()方法会原子性释放当前持有的锁,线程被唤醒后会重新竞争锁,只有成功拿到锁之后,才会从await()方法的阻塞位置继续向下执行。- 不要在
await()调用之后编写唤醒其他线程、释放锁类的逻辑,线程挂起后这部分代码完全没有执行机会。 - 所有加锁的逻辑都要把
unlock()放到finally块中执行,避免业务代码抛出异常时锁被永久持有。
内容的提问来源于stack exchange,提问作者codingcodingman
相关产品推荐
相关产品推荐

