Java多线程中wait()/notify()异常:部分线程获取NULL值求助
嘿,这个问题我之前也碰到过!核心问题其实不是notify()本身的实现,而是你的线程在被唤醒后没有重新检查等待条件,再加上数据的获取没有和等待逻辑做原子性绑定导致的。
问题根源拆解
首先要明确:标准的notify()只会唤醒一个等待在对象监视器上的线程(除非你误用了notifyAll()),但即便如此,仍然可能出现两种问题:
- 虚假唤醒(Spurious Wakeup):线程可能在没有收到合法通知的情况下被系统唤醒;
- 条件失效:哪怕是被正常唤醒,在它拿到锁开始执行的间隙,数据可能已经被其他线程抢先取走了。
这两种情况都会导致第二个线程(或者被虚假唤醒的线程)拿到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
相关产品推荐
相关产品推荐

