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

Java多线程中wait()/notify()异常:部分线程获取NULL值求助

嘿,这个问题我之前也碰到过!核心问题其实不是notify()本身的实现,而是你的线程在被唤醒后没有重新检查等待条件,再加上数据的获取没有和等待逻辑做原子性绑定导致的。

问题根源拆解

首先要明确:标准的notify()只会唤醒一个等待在对象监视器上的线程(除非你误用了notifyAll()),但即便如此,仍然可能出现两种问题:

  1. 虚假唤醒(Spurious Wakeup):线程可能在没有收到合法通知的情况下被系统唤醒;
  2. 条件失效:哪怕是被正常唤醒,在它拿到锁开始执行的间隙,数据可能已经被其他线程抢先取走了。

这两种情况都会导致第二个线程(或者被虚假唤醒的线程)拿到NULL数据。

正确的实现思路

1. 永远在循环里调用wait(),绝对不要用if判断

这是解决这类问题的核心准则!不要直接用if(条件不满足) wait(),必须用while(条件不满足) wait()。这样哪怕线程被虚假唤醒,或者被唤醒后条件已经不满足(数据被其他线程拿走),它会重新进入等待状态,而不是直接去拿数据。

伪代码示例(以Java为例):

// ❌ 错误写法:用if判断,会导致拿到NULL
synchronized(lockObj) {
    if(data == null) {
        lockObj.wait();
    }
    // 这里可能拿到NULL,因为被唤醒后data已经被其他线程取走
    processData(data);
    data = null;
}

// ✅ 正确写法:循环检查条件
synchronized(lockObj) {
    while(data == null) { // 每次被唤醒都重新确认条件
        lockObj.wait();
    }
    // 此时锁内确保data非空,安全处理
    processData(data);
    data = null;
}

2. 用队列管理待处理数据(多生产者/消费者场景)

如果你的场景是多个线程等待处理数据,单份数据的设计本身就不合理。应该用队列来存储待处理数据,每个被唤醒的线程从队列取走自己的任务,避免抢单冲突。

伪代码示例:

private Queue<Data> taskQueue = new LinkedList<>();
private final Object lock = new Object();

// 消费者线程逻辑
public void run() {
    while(true) {
        Data targetData = null;
        synchronized(lock) {
            // 队列空就等待
            while(taskQueue.isEmpty()) {
                lock.wait();
            }
            // 取出队首数据
            targetData = taskQueue.poll();
            // 如果队列还有数据,可以唤醒其他消费者
            if(!taskQueue.isEmpty()) {
                lock.notify();
            }
        }
        // 锁外处理数据,减少锁占用时间
        processData(targetData);
    }
}

// 生产者线程逻辑
public void produceData(Data newData) {
    synchronized(lock) {
        taskQueue.offer(newData);
        // 唤醒一个等待的消费者
        lock.notify();
    }
}

3. 合理选择notify()和notifyAll()

  • notify():适合只有一个线程能满足条件的场景,比如单消费者、单生产者,或者队列里只要有一个数据,唤醒一个线程就足够;
  • notifyAll():适合多个线程可能满足条件的场景(比如多个生产者等待队列有空位),但一定要配合循环检查条件,否则会出现多个线程被唤醒后,只有一个能拿到资源,其他线程需要重新等待。
额外注意事项
  • 所有wait()、notify()、notifyAll()的调用,必须在同一个对象的同步块/方法内执行,否则会抛出IllegalMonitorStateException;
  • 数据的修改、检查操作必须在锁的保护下,确保原子性,避免出现线程安全问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.26 09:47:51