为什么wait()、notify()方法需要和synchronized一起使用

本文深入探讨Java中线程通信的实现方式,重点讲解Obj.wait()与Obj.notify()的正确使用方法,及其与synchronized关键字的关系。通过实例分析,阐述了不正确使用所带来的错误,并解释了为何必须结合synchronized一起使用的原因。

提示:更多优秀博文请移步博主的GitHub仓库:GitHub学习笔记Gitee学习笔记

Obj.wait()与Obj.notify()必须要与synchronized(Obj)一起使用,也就是wait,与notify必须通过获取的锁对象进行调用**,从语法角度来说就是Obj.wait(),Obj.notify必须在synchronized(Obj){…}语句块内。从功能上来说wait就是说线程在获取对象锁后,主动释放对象锁,同时本线程休眠。直到有其它线程调用对象的notify()唤醒该线程,才能继续获取对象锁,并继续执行。相应的notify()就是对对象锁的唤醒操作。

1. 不一起使用导致的后果

public class Haha {
    public Map<String,String> map=new HashMap<>();
    public void haha() throws InterruptedException {
        synchronized (map){//使用map加锁
            System.out.println("haha method is run");
            this.wait();//用当前对象,调用wait方法会报错;正确的写法是map.wait()
        }
    }
}
public class Test {
    public static void main(String[] args) {
        new Thread(()->{
            try {
                new Haha().haha();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
    }
}

在上述代码中由于Haha类的haha方法中使用map加的锁,但又试图使用this.wait方法释放锁此时会报错:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PMmhumDw-1585273461081)(…/images/7.png)]

2. 为什么要在一起使用

Object.wait(),Object.notify(),Object.notifyAll()都是Object的方法,换句话说,就是每个类里面都有这些方法。

  • Object.wait():释放当前对象锁,并进入阻塞队列
  • Object.notify():唤醒当前对象阻塞队列里的任一线程(并不保证唤醒哪一个)
  • Object.notifyAll():唤醒当前对象阻塞队列里的所有线程

为什么这三个方法要与synchronized一起使用呢?解释这个问题之前,我们先要了解几个知识点

  • 每一个对象都有一个与之对应的监视器
  • 每一个监视器里面都有一个该对象的锁以及一个等待队列和一个同步队列

wait()方法的语义有两个,一是释放当前对象锁,另一个是进入阻塞队列,可以看到,这些操作都是与监视器相关的,当然要指定一个监视器才能完成这个操作了

notify()方法也是一样的,用来唤醒一个线程,你要去唤醒,首先你得知道他在哪儿,所以必须先找到该对象,也就是获取该对象的锁,当获取到该对象的锁之后,才能去该对象的对应的等待队列去唤醒一个线程。值得注意的是,只有当执行唤醒工作的线程离开同步块,即释放锁之后,被唤醒线程才能去竞争锁。

因wait()而导致阻塞的线程是放在阻塞队列中的,因竞争失败导致的阻塞是放在同步队列中的,notify()/notifyAll()实质上是把阻塞队列中的线程放到同步队列中去

为了便于理解,你可以把线程想象成一个个列车,对象想象成车站,每一个车站每一次能跑一班车,这样理解起来就比较容易了。

值得提的一点是,synchronized是一个非公平的锁,如果竞争激烈的话,可能导致某些线程一直得不到执行。

参考文章:https://blog.youkuaiyun.com/qq_39907763/article/details/79301813

<think>好的,用户问为什么Java的waitnotify必须在synchronized代码块使用。我需要先回忆相关知识,然后结合提供的引用资料来组织回答。 首先,我记得waitnotify是用于线程间协作的,但必须放在同步块中。可能的原因包括避免竞态条件确保原子性。根据引用资料,比如引用[5]提到无效唤醒的问题,也就是Lost Wakeup。这应该是一个关键点。 然后,引用[2]提到synchronized提供互斥访问可见性。互斥访问确保同一时间只有一个线程操作共享资源,而可见性保证线程对变量的修改对其他线程可见。因此,如果在非同步块中使用wait/notify,可能导致状态不一致。 还有,引用[1][4]中的代码示例展示了正确用法,即在同步方法或块内调用。这说明必须在持有锁的情况下调用这些方法,否则会抛出异常。比如,Java设计上就要求调用wait时必须拥有对象的监视器锁,否则抛出IllegalMonitorStateException。 接下来,需要分点解释原因。根据引用资料,可能的原因包括:防止竞态条件、确保原子性操作、避免无效唤醒,以及维持锁的一致性。例如,在调用wait之前检查条件,需要保证检查条件进入等待是原子操作,否则可能错过notify信号,导致线程永远等待。 另外,引用[5]提到的无效唤醒问题,即如果不在同步块中,可能在条件判断后,但在调用wait之前,其他线程修改了条件并调用notify,导致本线程错过通知,从而永久等待。同步块通过原子地释放锁进入等待状态来解决这个问题。 还需要提到Java的设计机制,如调用wait时会释放锁,而notify不会立即释放锁,必须等到同步块结束。这涉及到线程状态的转换锁的管理,同步机制在这里起到协调作用。 可能用户还想知道如果不这样做会怎样,比如抛出异常或者出现不可预期的行为。比如,在未同步的情况下,多个线程可能同时修改共享变量,导致数据不一致,或者wait方法无法正确获取锁状态,从而引发异常。 最后,总结这些原因,强调同步块的必要性,确保线程安全正确协作。同时,结合提供的引用资料中的例子,如SharedResource类,说明正确用法潜在问题。</think>### Java的wait/notify为何必须在synchronized中调用? #### 1. **保证原子性操作** 当线程调用$wait()$时,需要完成两个原子操作: - **释放对象锁**:通过$synchronized$获取的监视器锁会被自动释放 - **进入等待状态****:将线程状态从RUNNABLE改为WAITING 这两个操作必须作为不可分割的整体执行。若没有$synchronized$保护,可能在释放锁改变线程状态之间发生线程切换,导致状态不一致[^5]。 #### 2. **防止竞态条件** 考虑经典的生产者-消费者场景: ```java // 错误示例(无同步) if (queue.isEmpty()) { queue.wait(); // 可能错过notify信号 } ``` 在非同步块中,可能在检查`isEmpty()`之后、调用`wait()`之前,其他线程调用了`notify()`,导致信号丢失(Lost Wakeup)。同步块保证了条件判断等待操作的原子性[^1][^5]。 #### 3. **维护锁的一致性** Java要求调用$wait()/notify()$时必须持有对象监视器锁: - 调用$wait()$时自动释放锁 - 被唤醒后重新竞争锁 - $notify()$调用线程不会立即释放锁,直到退出同步块 这种机制保证了线程对共享资源的访问始终处于受控状态[^2][^4]。 #### 4. **避免无效唤醒** 当使用同步块时,推荐将条件检查放在循环中: ```java synchronized(lock) { while (!condition) { // 循环检查 lock.wait(); } } ``` 这种方式能有效防止: - 伪唤醒(线程未被notify就唤醒) - 多个线程同时修改共享数据导致的逻辑错误[^3] #### 5. **强制语法约束** Java语言规范明确规定: - 调用$wait()/notify()$时未持有锁会抛出`IllegalMonitorStateException` - 这是编译器无法检测的运行时异常,强制开发者遵循线程安全规范[^1] --- ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

听到微笑

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值