Java并发编程之Wait/Notify

一、Monitor对象的核心结构

每个Java对象都关联一个Monitor对象,其核心字段包括:

  1. owner:指向当前持有锁的线程。
  2. EntryList:存放竞争锁的线程队列(阻塞状态)。
  3. WaitSet:存放调用wait()后释放锁的线程队列(等待状态)。

二、wait()notify()的执行逻辑

1. wait()方法的作用
  • 释放锁:当前线程释放对象的Monitor锁。
  • 进入等待队列:线程被放入对象的WaitSet队列中(等待被唤醒)。
  • 挂起线程:线程进入WAITINGTIMED_WAITING状态,直到被notify()唤醒或超时。
2. notify()方法的作用
  • 唤醒线程:从WaitSet中随机唤醒一个线程,将其移动到EntryList队列中(重新竞争锁)。
  • 不释放锁:调用notify()的线程不会立即释放锁,需退出同步块后锁才释放。
3. notifyAll()方法的作用
  • 唤醒WaitSet中所有等待线程,将它们全部移动到EntryList

三、关键规则与注意事项

  1. 必须在同步块内调用
    wait()/notify()必须持有对象的Monitor锁,否则抛出IllegalMonitorStateException

  2. 条件检查必须用while而非if
    避免虚假唤醒(Spurious Wakeup),即使未被通知,线程也可能从wait()返回。例如:

    synchronized (lock) {
        while (条件不满足) { // 必须用while循环检查条件
            lock.wait();
        }
        // 执行操作
    }
    
  3. 唤醒策略

    • notify():随机唤醒一个线程,适合单生产者-单消费者场景。
    • notifyAll():唤醒所有线程,确保不会遗漏等待线程(更安全,但可能增加竞争)。
  4. 锁释放与重入

    • wait()会释放锁,唤醒后需重新竞争锁才能继续执行。
    • notify()不会释放锁,线程需退出同步块才会释放锁。

四、执行流程图解

生产者线程:
1. 获取锁 → 检查条件(消息是否已消费)
   ↓ 条件满足
2. 生产消息 → 设置empty=false → 调用notifyAll()
3. 退出同步块 → 释放锁

消费者线程:
1. 获取锁 → 检查条件(消息是否未消费)
   ↓ 条件满足
2. 消费消息 → 设置empty=true → 调用notifyAll()
3. 退出同步块 → 释放锁

等待线程(WaitSet):
1. 被notifyAll()唤醒 → 移动到EntryList
2. 重新竞争锁 → 获取锁后继续执行

六、常见问题

1. 为什么wait()notify()属于Object类?
  • 因为它们依赖对象的Monitor锁,而每个对象都可作为锁载体。
2. 与sleep()的区别?
  • sleep():不释放锁,线程暂停指定时间。
  • wait():释放锁,线程进入等待状态直到被唤醒。
3. 如何避免死锁?
  • 确保所有wait()都有对应的notify()唤醒。
  • 避免嵌套锁或长时间持有锁。

七、总结

通过wait()/notify()配合Monitor锁,可以实现线程间的精准协作:

  • wait():释放锁并等待条件满足。
  • notify():通知等待线程条件已变化。
    正确使用这两个方法需严格遵循同步规则和条件检查,避免线程饥饿或死锁问题。

拓展:wait与sleep

1. 所属类与调用方式

方法所属类调用方式
wait()Object必须在同步块中调用(需持有对象锁)
sleep()Thread静态方法,可直接调用(无需持有锁)

2. 锁的释放行为

方法是否释放锁?锁释放时机
wait()释放锁调用wait()后立即释放锁,线程进入等待状态
sleep()不释放锁线程休眠期间一直持有锁,其他线程无法进入同步块

3. 唤醒机制

方法唤醒方式中断响应
wait()需其他线程调用notify()/notifyAll()响应中断(抛出InterruptedException
sleep()超时后自动唤醒,或通过interrupt()中断响应中断(抛出InterruptedException

4. 关键区别总结

特性wait()sleep()
锁释放立即释放锁不释放锁
调用条件必须在同步块中调用任意位置调用
唤醒方式依赖notify()/notifyAll()超时或中断唤醒
用途线程间协作(依赖共享资源条件)单纯暂停线程执行

5. 常见误区

  1. 在非同步块中调用wait()
    会导致 IllegalMonitorStateException

  2. sleep()代替wait()
    可能导致死锁(线程休眠期间不释放锁,其他线程无法获取锁)。

  3. 虚假唤醒(Spurious Wakeup)
    wait()返回后必须用while循环重新检查条件,而非if

  4. 中断处理
    两者均需捕获InterruptedException,但wait()唤醒后需重新竞争锁。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值