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

Java多线程问题:notifyAll()无法在共享锁上唤醒线程的原因排查

问题根源:误解了wait()/notify()和InterruptedException的关系

兄弟,你这里的核心问题完全是对Java线程协作机制的误解——notifyAll()根本不会触发InterruptedException!这个异常只有当线程被主动调用interrupt()方法时才会抛出,和唤醒等待线程的notify系列方法半毛钱关系都没有。你的代码逻辑错把唤醒当成了中断信号,自然会导致两个线程都陷入等待,程序阻塞。

先给你拆解下现有代码的死循环过程:

  1. 前端线程先进入同步块,调用notifyAll()(这时候后端线程还没进入同步块,所以没效果),然后调用wait()进入等待。
  2. 后端线程接着进入同步块,调用notifyAll()(这时候前端线程在等待,会被唤醒,但前端线程醒过来后会检查allCaughtUp还是false,于是又走一遍循环:先notifyAll(),再wait())。
  3. 现在两个线程都卡在wait()上,没人再调用notifyAll()来唤醒它们,程序直接阻塞。

正确实现:用状态变量配合wait()/notify()做线程协作

你的业务场景是典型的前后端线程交替协作:前端等后端准备数据,后端等前端完成输入再准备下一批。我们应该用明确的状态变量来标记阶段,配合wait()/notify()来实现,而不是错误地依赖异常。

这里给你写个修正后的示例代码,完全贴合你的业务场景:

public class FrontBackCooperation {
    // 共享锁对象
    static final Object lock = new Object();
    // 标记后端是否准备好数据
    static boolean dataReady = false;
    // 标记前端是否完成输入
    static boolean inputDone = false;
    // 模拟多轮数据交互,你可以根据需求调整
    static final int ROUND = 3;

    public static void main(String[] args) {
        // 启动后端线程
        new Thread(() -> backendTask()).start();
        // 前端主线程执行任务
        frontendTask();
    }

    /**
     * 前端线程逻辑:等待数据 -> 处理展示/用户输入 -> 通知后端
     */
    private static void frontendTask() {
        for (int i = 1; i <= ROUND; i++) {
            synchronized (lock) {
                // 等待后端准备好数据,用while防止虚假唤醒
                while (!dataReady) {
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        // 这里的异常是处理线程被中断的情况,不是协作信号
                        Thread.currentThread().interrupt();
                        return;
                    }
                }
                // 模拟前端处理数据/等待用户输入
                System.out.printf("第%d轮:前端拿到数据,展示并等待用户输入...%n", i);
                try {
                    Thread.sleep(1000); // 模拟用户输入耗时
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    return;
                }
                // 标记输入完成,唤醒后端线程
                inputDone = true;
                dataReady = false;
                lock.notifyAll();
            }
        }
        System.out.println("前端任务全部完成");
    }

    /**
     * 后端线程逻辑:准备数据 -> 通知前端 -> 等待输入完成 -> 准备下一批
     */
    private static void backendTask() {
        for (int i = 1; i <= ROUND; i++) {
            synchronized (lock) {
                // 模拟后端准备数据
                System.out.printf("第%d轮:后端正在准备数据...%n", i);
                try {
                    Thread.sleep(1500); // 模拟数据准备耗时
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    return;
                }
                // 标记数据准备好,唤醒前端线程
                dataReady = true;
                inputDone = false;
                lock.notifyAll();
                // 等待前端完成输入
                while (!inputDone) {
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                        return;
                    }
                }
                System.out.printf("第%d轮:后端收到输入完成信号,准备下一批数据...%n", i);
            }
        }
        System.out.println("后端任务全部完成");
    }
}

关键要点总结

  • 永远在while循环中检查状态:这是为了处理「虚假唤醒」——线程可能会在没有被notify的情况下醒来,必须重新检查状态确认是否可以继续执行。
  • notify/notifyAll的作用是唤醒等待线程:被唤醒的线程会从wait()方法处继续执行,然后重新检查状态,而不是抛出异常。
  • InterruptedException是用来处理线程中断的:比如外部需要终止线程时调用interrupt(),这时候才会触发这个异常,不要用它来做线程协作的信号。

内容的提问来源于stack exchange,提问作者Thorbear

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.29 04:43:11