为什么wait()和notify()需要搭配synchonized关键字使用

本文围绕Java多线程展开,介绍了synchronized含义,它用于实现多线程同步。还阐述了wait()、notify()和notifyAll()的功用,它们需与synchronized搭配用于线程同步。重点分析了wait()和notify()或notifyAll()搭配synchronized的原因,通过示例展示了正确用法。

##理解此问题先修知识:

synchronized 的含义:

Java中每一个对象都可以成为一个监视器(Monitor), 该Monitor由一个锁(lock), 一个等待队列(waiting queue ), 一个入口队列( entry queue).
对于一个对象的方法, 如果没有synchronized关键字, 该方法可以被任意数量的线程,在任意时刻调用。
对于添加了synchronized关键字的方法,任意时刻只能被唯一的一个获得了对象实例锁的线程调用。
synchronized用于实现多线程的同步操作
wait()功用

wait(), notify(), notifyAll() 和 synchonized 需要搭配使用, 用于线程同步
wait()总是在一个循环中被调用,挂起当前线程来等待一个条件的成立。 Wait调用会一直等到其他线程调用notifyAll()时才返回。
当一个线程在执行synchronized 的方法内部,调用了wait()后, 该线程会释放该对象的锁, 然后该线程会被添加到该对象的等待队列中(waiting queue), 只要该线程在等待队列中, 就会一直处于闲置状态, 不会被调度执行。 要注意wait()方法会强迫线程先进行释放锁操作,所以在调用wait()时, 该线程必须已经获得锁,否则会抛出异常。由于wait()在synchonized的方法内部被执行, 锁一定已经获得, 就不会抛出异常了。

notify()的功用

wait(), notify(), notifyAll() 和 synchonized 需要搭配使用, 用于线程同步
当一个线程调用一个对象的notify()方法时, 调度器会从所有处于该对象等待队列(waiting queue)的线程中取出任意一个线程, 将其添加到入口队列( entry queue) 中. 然后在入口队列中的多个线程就会竞争对象的锁, 得到锁的线程就可以继续执行。 如果等待队列中(waiting queue)没有线程, notify()方法不会产生任何作用
notifyAll() 和notify()工作机制一样, 区别在于notifyAll()会将等待队列(waiting queue)中所有的线程都添加到入口队列中(entry queue)
注意, notifyAll()比notify()更加常用, 因为notify()方法只会唤起一个线程, 且无法指定唤醒哪一个线程,所以只有在多个执行相同任务的线程在并发运行时, 我们不关心哪一个线程被唤醒时,才会使用notify()
##为什么wait()和notify()或notifyAll()需要搭配synchronized关键字使用

从语义角度来讲, 一个线程调用了wait()之后, 必然需要由另外一个线程调用notify()来唤醒该线程, 所以本质上, wait()与notify()的成对使用, 是一种线程间的通信手段。
进一步分析, wait() 操作的调用必然是在等待某种条件的成立, 而条件的成立必然是由其他的线程来完成的。 所以实际上, 我们调用 wait() 的时候, 实际上希望达到如下的效果
// 线程A 的代码
while(!condition){ // 不能使用 if , 因为存在一些特殊情况, 使得线程没有收到 notify 时也能退出等待状态
    wait();
}
// do something
1
2
3
4
5
// 线程 B 的代码
if(!condition){ 
    // do something ...
    condition = true;
    notify();
}
1
2
3
4
5
6
现在考虑, 如果wait() 和 notify() 的操作没有相应的同步机制, 则会发生如下情况
【线程A】 进入了 while 循环后(通过了 !condition 判断条件, 但尚未执行 wait 方法), CPU 时间片耗尽, CPU 开始执行线程B的代码
【线程B】 执行完毕了 condition = true; notify(); 的操作, 此时【线程A】的 wait() 操作尚未被执行, notify() 操作没有产生任何效果
【线程A】执行wait() 操作, 进入等待状态,如果没有额外的 notify() 操作, 该线程将持续在 condition = true 的情形下, 持续处于等待状态得不到执行。
由此看出, 在使用 wait() 和 notify() 这种会挂起线程的操作时, 我们需要一种同步机制保证, condition 的检查与 wait() 操作, 以及 condition 的更新与 notify() 是互斥的。

那是否简单的将之前的代码包裹在一个 synchronized 代码块中就可以满足需求呢? 像下面这样。
// 线程A 的代码
synchronized(obj_A)
{
    while(!condition){ 
        wait();
    }
    // do something 
}
1
2
3
4
5
6
7
8
// 线程 B 的代码
synchronized(obj_A)
{
    if(!condition){ 
        // do something ...
        condition = true;
        notify();
    }
}
1
2
3
4
5
6
7
8
9
乍一看, 上述的代码可以解决问题, 但是仔细分析一下, 由于wait() 操作会挂起当前线程, 那么必然需要在挂起前释放掉 obj_A 的锁, 但如果 obj_A 允许是任意对象, wait() 函数作为一个没有参数输入的方法,无从得知应该释放哪个对象的锁 。于是很自然的, 语法就会被设计成 java 现在的样子。即基于对象的 wait() 与 notify() 的调用, 必须先获得该对象的锁。

正确的用法示例如下

// 线程 A 的代码
synchronized(obj_A)
{
    while(!condition){ 
        obj_A.wait();
    }
    // do something 
}
1
2
3
4
5
6
7
8
// 线程 B 的代码
synchronized(obj_A)
{
    if(!condition){ 
        // do something ...
        condition = true;
        obj_A.notify();
    }
}

原文:https://blog.youkuaiyun.com/lengxiao1993/article/details/52296220 

Java多线程编程中,`wait()` `notify()` 方法通常需要配合使用,这是因为它们各自承担着不同的职责,并且只有结合使用才能实现线程间的协调通信。 ### `wait()` 方法的作用 `wait()` 方法用于使当前线程进入等待状态,直到其他线程调用该对象的 `notify()` 或 `notifyAll()` 方法。调用 `wait()` 会释放当前持有的锁,从而使线程暂停执行并让出CPU资源。这种方式非常适合用于线程间基于共享资源的状态同步[^2]。 例如,在生产者-消费者问题中,消费者线程可能需要等待生产者线程生成新的数据项。此时,消费者线程可以在某个条件变量上等待(如一个队列为空时),而生产者线程在添加新数据后通知等待的消费者线程继续处理。 ### `notify()` 方法的作用 `notify()` 方法则用于唤醒一个正在等待该对象监视器的线程。当某个线程修改了共享资源的状态后,它可以通过调用 `notify()` 来通知那些因特定条件未满足而处于等待状态的线程[^3]。 需要注意的是,`notify()` 只能唤醒一个等待线程,而 `notifyAll()` 则会唤醒所有等待的线程。选择哪个方法取决于具体的应用场景需求。 ### 为什么必须同时使用? 1. **确保线程间通信的有效性**:`wait()` `notify()` 是 Java 中实现线程间协作的关键机制之一。通过 `wait()`,线程可以基于某些条件挂起;通过 `notify()`,另一个线程可以在条件改变时唤醒这些被挂起的线程。如果只使用其中一个,则无法完成完整的通信过程[^4]。 2. **防止虚假唤醒**:虽然理论上 `wait()` 可以单独用来阻塞线程,但现实中可能会发生所谓的“虚假唤醒”现象——即使没有调用 `notify()`,线程也可能意外地从 `wait()` 返回。因此,通常建议将 `wait()` 放在一个循环中检查实际条件是否成立,从而保证程序逻辑的正确性。 3. **维护数据一致性**:由于 `wait()` `notify()` 都要求操作必须发生在同步上下文中(即由 `synchronized` 关键字保护的代码块或方法内),这有助于确保对共享资源访问的安全性。不恰当地使用这两个方法可能导致死锁或其他并发问题[^1]。 以下是一个简单的示例代码,展示了如何正确地在同一段程序中使用 `wait()` `notify()`: ```java public class WaitNotifyExample { private final Object lock = new Object(); private boolean ready = false; public void waitForReady() { synchronized (lock) { while (!ready) { try { lock.wait(); // 等待直到ready变为true } catch (InterruptedException e) { Thread.currentThread().interrupt(); return; } } System.out.println("准备就绪"); } } public void setReady() { synchronized (lock) { ready = true; lock.notify(); // 唤醒等待中的线程 } } public static void main(String[] args) throws InterruptedException { WaitNotifyExample example = new WaitNotifyExample(); Thread t1 = new Thread(() -> { example.waitForReady(); }); Thread t2 = new Thread(() -> { try { Thread.sleep(2000); // 模拟延迟 } catch (InterruptedException e) { Thread.currentThread().interrupt(); } example.setReady(); }); t1.start(); t2.start(); t1.join(); t2.join(); } } ``` 在这个例子中,主线程启动两个子线程:一个是等待条件为真的线程 (`t1`),另一个是在一段时间后设置条件并通知等待线程的线程 (`t2`)。这种模式有效地演示了 `wait()` `notify()` 在实际应用中的协同工作方式。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值