You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ReentrantLock调用signal()线程未唤醒 逻辑仅执行一次问题排查

问题根因

signal()方法本身没有失效,代码出现永久阻塞的核心原因是await()与signal()的执行顺序写反,再加上缺少轮次状态校验,最终导致两个线程全部进入永久等待状态。
具体问题点如下:

  • 逻辑顺序完全颠倒:现有写法是先调用await()挂起当前线程,再执行signal()唤醒目标线程。await()方法会直接阻塞当前执行流,一旦线程进入条件等待队列,await()后续的代码不会有任何执行机会,相当于唤醒逻辑永远不会被触发。
  • 第一轮执行就会触发死锁,流程如下:
    1. 学生线程没有启动延迟,率先抢到锁执行完提问流程,调用forStudent.await():此时学生线程自动释放锁、进入forStudent对应的等待队列挂起,后续的forJoGyo.signal()根本没有执行机会。
    2. 助教线程初始100ms的休眠结束后,成功抢到锁执行完自己的提问流程,调用forJoGyo.await():助教线程同样自动释放锁、进入forJoGyo对应的等待队列挂起,后续的forStudent.signal()也无法执行。
    3. 此时两个工作线程全部处于等待状态,没有任何活跃线程持有锁、也没有线程能调用唤醒方法,程序自然会卡住不再继续执行。
  • 缺少轮次控制标记:就算调整了唤醒顺序,没有状态位校验当前执行权限的话,一旦线程意外抢占到锁,会直接打破交替执行的规则,甚至再次触发阻塞。
修复方案

核心调整三点:一是将唤醒逻辑移到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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 06:57:20