JAVA多线程-Object.wait(),Object.notify(),Object.notifyAll()

本文深入探讨Java中线程的同步机制,包括wait(), notify()和notifyAll()方法的使用,以及它们与synchronized关键字的关系。通过代码实操演示如何实现线程间的交替打印,解析线程状态变化及唤醒策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

初步理解

wait()

  1. 使线程停止运行,进入等待队列,会立刻释放对象锁
  2. wait(0) 代表的是无限期的等待,不能自动唤醒。
  3. java.lang.Object类方法。

notify()

  1. 唤醒一个正在等待当前对象的waiting线程,遵循FIFO(先进先出)策略。
  2. notify()调用后,并不是马上就释放对象锁,而是在相应的synchronized{…}语句块执行结束后,自动释放锁后,JVM在wait()对象锁的线程中随机选取一线程赋予其对象锁,唤醒线程,继续执行。这样就提供了在线间同步、唤醒的操作
  3. java.lang.Object类方法。

notifyAll()

  1. 唤醒正在等待当前对象的所有等待线程。
  2. 默认的唤醒策略是:LIFO(后进先出)。
  3. java.lang.Object类方法。

wait()与notify()

  1. Obj.wait(),与Obj.notify()必须与synchronized(Obj)一起使用,也就是wait()是针对已经获取了Obj锁进行操作。
  2. 语法角度来说就是Obj.wait(),Obj.notify()必须在、synchronized(Obj){…}语句块内。
  3. 功能上来说wait就是指在该线程获取对象锁后,主动释放对象锁,同时本线程休眠。notify()就是对对象锁的唤醒操作。

wait()与sleep()
共同点

  1. 都可以指定线程阻塞的时间。
  2. 都可以通过interrupt()方法打断(不建议使用)线程的暂停状态 ,从而使线程立刻抛出InterruptedException。如果线程A希望立即结束线程B,则可以对线程B对应的Thread实例调用interrupt方法。如果此刻线程B正在wait/sleep /join,则线程B会立刻抛出InterruptedException,在catch() {} 中直接return即可安全地结束线程。
    需要注意的是,InterruptedException是线程自己从内部抛出的,并不是interrupt()方法抛出的。对某一线程调用 interrupt()时,如果该线程正在执行普通的代码,那么该线程根本就不会抛出InterruptedException。但是,一旦该线程进入到 wait()/sleep()/join()后,就会立刻抛出InterruptedException 。

不同点

  1. sleep()是Thread类的方法,wait()是Object类的方法。
  2. sleep()方法没有释放锁,而wait()方法释放了锁。
  3. wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用。

执行notify()后,waiting线程可能的状态

线程a先抢到了对象o的锁,然后wait,然后b抢到了o的锁,然后b中调用o.notify并释放锁,此时a是running状态还是blocked状态??
如果b在执行完notify()后没有释放锁则线程a是阻塞等待,
如果线程b执行完同步代码块(释放锁)后,则线程a就是就绪态,不一定是运行态

代码实操,三线程打印ABC

建立三个线程,A线程打印10次A,B线程打印10次B,C线程打印10次C,要求线程同时运行,交替打印10次ABC。这个问题用Object的wait(),notify()就可以很方便的解决。

public class T08WaitNotify {

    class T implements Runnable{
        // 自定义线程名称
        private String name;
        // 前一个obj
        private Object prev;
        // 当前obj
        private Object self;

        T(String name, Object prev, Object self){
            this.name = name;
            this.prev = prev;
            this.self = self;
        }

        @Override
        public void run() {
            int count = 0;
            while (count < 10) {
                synchronized (prev) {
                    synchronized (self) {
                        System.out.print(name);
                        count++;
                        self.notify();
                    }
                    try {
                        prev.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

            }
        }
    }

    public static void main(String[] args) throws InterruptedException {

        T08WaitNotify obj = new T08WaitNotify();

        Object oa = new Object();
        Object ob = new Object();
        Object oc = new Object();

        T a = obj.new T("A", oc, oa);
        T b = obj.new T("B", oa, ob);
        T c = obj.new T("C", ob, oc);

        Thread ta = new Thread(a);
        Thread tb = new Thread(b);
        Thread tc = new Thread(c);

        ta.start();
        Thread.sleep(1000); // 确保按照A、B、C顺序执行
        tb.start();
        Thread.sleep(1000);
        tc.start();
        Thread.sleep(1000);
    }
}

代码流程说明
A线程启动,依次取得c、a锁,打印A,执行notify()唤醒任意一个a对象等待池中的线程,待synchronized(self){}块执行完后释放a锁,执行wait(),将A线程放入c对象相关的等待池中等待被唤醒同时释放c锁; 1秒后,B线程启动,依次取得a、b锁,打印B,执行notify()唤醒任意一个b对象等待池中的线程,待synchronized(self){}块执行完后释放b锁,执行wait(),将B线程放入a对象相关的等待池中等待被唤醒,同时释放a锁;1秒后,C线程启动,依次取得b、c锁,打印C,执行notify()唤醒任意一个c对象等待池中的线程(此时A线程被唤醒),待synchronized(self){}块执行完后释放c锁,执行wait(),将C线程放入b对象相关的等待池中等待被唤醒,同时释放b锁。

至此,完成一次ABC的打印。继续执行,从A线程开始,执行各自线程中的下一次循环。

特别说明
Java源码中wait()默认调用的是wait(0),表明线程无限等待,只能被其他线程唤醒。

参考
https://blog.youkuaiyun.com/u010002184/article/details/82912225
https://blog.youkuaiyun.com/hj1997a/article/details/84284973

<think>我们正在讨论JavaObject.wait()notifyAll()方法的使用方法和注意事项。根据提供的引用,特别是引用[2]和[3],我们可以总结如下: 1. **基本用法**: - `wait()`: 使当前线程进入等待状态,直到其他线程调用该对象的`notify()`或`notifyAll()`方法,或者被中断。 - `notifyAll()`: 唤醒在该对象上等待的所有线程。注意,被唤醒的线程将竞争该对象的锁。 2. **使用前提**: - 这些方法必须在同步代码块或同步方法中调用,即当前线程必须持有该对象的锁(monitor),否则会抛出`IllegalMonitorStateException`[^2]。 - 通常,我们使用`synchronized`关键字来获取对象的锁。 3. **典型使用模式**: - 线程在检查某个条件不满足时,调用`wait()`释放锁并等待。 - 当另一个线程改变了条件,并调用`notify()`或`notifyAll()`来唤醒等待的线程。 - 被唤醒的线程会重新尝试获取锁,一旦获取成功,就从`wait()`返回并继续执行。 4. **注意事项**: - 使用`wait()`时,通常需要在循环中检查条件,因为被唤醒时条件可能仍未满足(虚假唤醒)[^3]。 - 使用`notifyAll()`通常比`notify()`更安全,因为它会唤醒所有等待的线程,避免某些线程永远不被唤醒的风险。但是,如果只需要唤醒一个线程,可以使用`notify()`。 5. **与Condition的对比**: -Java 1.5之后,引入了`Condition`接口,它提供了更灵活的线程间通信机制。`Condition`的`await()`和`signal()`/`signalAll()`方法类似于`wait()`和`notify()`/`notifyAll()`,但`Condition`可以绑定到多个等待条件上,使用更安全高效[^3]。 6. **示例场景**: - 生产者-消费者问题:当缓冲区满时,生产者等待;当缓冲区空时,消费者等待。通过`wait()`和`notifyAll()`协调生产和消费。 下面是一个简单的示例,展示如何使用`wait()`和`notifyAll()`: ```java public class SharedObject { private boolean ready = false; public synchronized void doWait() throws InterruptedException { while (!ready) { // 循环检查条件,防止虚假唤醒 wait(); } // 执行条件满足后的操作 } public synchronized void doNotify() { ready = true; notifyAll(); // 唤醒所有等待线程 } } ``` 在这个例子中,线程调用`doWait()`方法时,如果条件(ready为true)不满足,就会等待。另一个线程调用`doNotify()`方法设置条件为true,并唤醒所有等待的线程。被唤醒的线程重新检查条件,如果满足则继续执行。 **注意事项**: - 在调用`wait()`方法时,会释放锁,以便其他线程可以获取锁并调用`notifyAll()`。 - 当线程被唤醒时,它需要重新获取锁才能从`wait()`返回。 **与Condition的对比**: 使用`Lock`和`Condition`可以更灵活地控制线程的等待和唤醒。例如,我们可以创建多个条件(Condition)对象,让不同的线程等待不同的条件。而使用`Object`的监视器方法,所有的等待线程都在同一个条件上等待,无法区分。 例如,使用`Condition`实现生产者-消费者: ```java Lock lock = new ReentrantLock(); Condition notFull = lock.newCondition(); Condition notEmpty = lock.newCondition(); // 生产者 lock.lock(); try { while (buffer.isFull()) { notFull.await(); // 等待不满 } buffer.add(item); notEmpty.signalAll(); // 唤醒消费者 } finally { lock.unlock(); } // 消费者 lock.lock(); try { while (buffer.isEmpty()) { notEmpty.await(); // 等待不空 } buffer.remove(); notFull.signalAll(); // 唤醒生产者 } finally { lock.unlock(); } ``` 这样,生产者和消费者可以等待不同的条件,从而更高效地唤醒特定类型的线程。 总结: - 在需要线程间协调时,使用`wait()`和`notifyAll()`(或`notify()`)是基本方法,但要注意使用在同步块中,并在循环中检查条件。 - 在复杂场景下,推荐使用`Condition`,它提供了更精细的控制。 §§相关问题§§ 1. 什么是虚假唤醒(spurious wakeup)?如何避免? 2. 为什么使用`notifyAll()`比`notify()`更安全? 3. 在分布式系统中,如何实现类似`wait()`和`notifyAll()`的线程协调机制?</think>### JavaObject.wait()notifyAll() 的使用方法与注意事项 #### **一、核心机制** 1. **线程协作基础** - `wait()`: 使当前线程**释放锁并进入等待状态**,需被其他线程唤醒[^2][^3] - `notifyAll()`: **唤醒所有**在该对象监视器(monitor)上等待的线程[^3][^4] - 二者必须配合 `synchronized` 使用,否则抛出 `IllegalMonitorStateException`[^2] 2. **典型生产者-消费者模式** ```java class Message { private String content; private boolean empty = true; public synchronized String read() { while(empty) { // 必须用 while 循环检查条件 try { wait(); } // 释放锁并等待 catch (InterruptedException e) {} } empty = true; notifyAll(); // 唤醒所有等待线程 return content; } public synchronized void write(String value) { while(!empty) { // 循环检查条件 try { wait(); } catch (InterruptedException e) {} } content = value; empty = false; notifyAll(); // 通知所有读线程 } } ``` #### **二、使用步骤** 1. **获取对象锁** 在 `synchronized` 代码块中调用方法[^2]: ```java synchronized (sharedObject) { while(conditionNotMet) { // 必须用循环检查条件 sharedObject.wait(); // 释放锁并等待 } // ... 执行业务逻辑 sharedObject.notifyAll(); // 唤醒所有等待线程 } ``` 2. **多线程协调示例** ```java public static void main(String[] args) { Message message = new Message(); new Thread(new Reader(message), "R1").start(); // 读线程1 new Thread(new Reader(message), "R2").start(); // 读线程2 new Thread(new Writer(message), "W").start(); // 写线程 } ``` - 当写线程调用 `write()` 时,唤醒所有读线程[^4][^5] - 读线程通过 `while(empty)` 循环避免虚假唤醒 #### **三、关键注意事项** 1. **必须使用循环检查条件** ```java while(!condition) { // ❌ 错误:if (!condition) wait(); // ✅ 正确:while循环防止虚假唤醒 } ``` 虚假唤醒(spurious wakeup)可能导致条件未满足时线程被唤醒,需循环验证[^3]。 2. **notify() vs notifyAll()** | 方法 | 唤醒对象 | 适用场景 | |------|----------|----------| | `notify()` | 随机一个线程 | 单生产者-单消费者 | | `notifyAll()` | **所有等待线程** | 多生产者-多消费者(更安全)[^3][^4] | 3. **锁对象一致性** `wait()`/`notifyAll()` 必须使用**同一个对象锁**,否则导致监视器不匹配[^2]: ```java Object lock1 = new Object(); Object lock2 = new Object(); // ❌ 错误:不同锁对象 synchronized(lock1) { lock2.wait(); } ``` 4. **超时机制** 使用 `wait(long timeout)` 避免永久阻塞: ```java synchronized(lock) { if(!condition) { lock.wait(5000); // 最多等待5秒 } } ``` #### **四、Condition 的替代方案** Java 1.5+ 推荐使用 `Condition` 替代,提供更精细控制[^3]: ```java Lock lock = new ReentrantLock(); Condition condition = lock.newCondition(); lock.lock(); try { while(!conditionMet) { condition.await(); // 类似 wait() } condition.signalAll(); // 类似 notifyAll() } finally { lock.unlock(); } ``` 优势:支持多个等待队列、可中断锁、公平锁等高级特性。 #### **五、典型应用场景** 1. **线程池任务协调** 主线程等待所有子任务完成(替代 `join()`) 2. **生产者-消费者队列** 缓冲区满时阻塞生产者,空时阻塞消费者 3. **资源初始化屏障** 确保所有依赖资源就绪后再启动主逻辑 > ⚠️ **重要原则**:永远在循环中调用 `wait()`,始终使用 `notifyAll()` 除非明确知道只需唤醒单个线程[^3][^4]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值