Java多线程中wait()和notify()

本文通过实例详细介绍了Java中wait()和notify()方法的工作原理及其应用。通过三个线程的交互,实现按顺序打印字符的功能,展示了如何利用这些方法解决线程间的同步问题。

wait():线程在获取对象锁之后,主动释放对象锁,同时,让本线程休眠。直到有其它线程调用对象的notify()唤醒该线程,才能继续获取对象锁,并继续执行。
notify():对对象锁的唤醒操作。

注意:
1、Obj.wait()和Obj.notify()必须要与synchronized(Obj)一起使用,也就是wait,与notify是针对已经获取了Obj锁进行操作,从语法角度来说就是Obj.wait(),Obj.notify必须在synchronized(Obj){…}语句块内。
2、notify()调用后,并不是马上就释放当前线程的对象锁而唤醒相应线程的对象锁,而是在相应的synchronized(){}语句块执行结束,自动释放锁后,JVM会在wait()对象锁的线程中随机选取一线程,赋予其对象锁,唤醒线程,继续执行。这样就提供了在线程间同步、唤醒的操作。
3、Thread.sleep()与Object.wait()二者都可以暂停当前线程,释放CPU控制权,主要的区别在于Object.wait()在释放CPU同时,释放了对象锁的控制。

下面看一个例子:
建立三个线程,A线程打印10个”A”,B线程打印10个”B”,C线程打印10个”C”,要求线程同时运行,交替打印10次ABC。这个问题用Object的wait(),notify()就可以很方便的解决。代码如下:

public class ThreadTest2 {
    public static void main(String[] args) throws InterruptedException {
        Object a=new Object();
        Object b=new Object();
        Object c=new Object();
        Thread ta=new Thread(new ThreadPrint("A",c,a));
        Thread tb=new Thread(new ThreadPrint("B",a,b));
        Thread tc=new Thread(new ThreadPrint("C",b,c));

        ta.start();
        Thread.sleep(100);
        tb.start();
        Thread.sleep(100);
        tc.start();
        Thread.sleep(100);
    }
}

class ThreadPrint implements Runnable{
    private String name;
    //前一个对象锁
    private Object prev;
    //当前对象锁
    private Object self;

    public ThreadPrint(String name, Object prev, Object self){
        this.name=name;
        this.prev=prev;
        this.self=self;
    }
    public void run() {
        int count=10;
        while(count>0){
            synchronized(prev) {
                synchronized(self) {
                    System.out.print(name);
                    count--;
                    //唤醒当前对象锁
                    self.notify();
                }
                try {
                    //释放前一个对象锁
                    prev.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

输出结果:ABCABCABCABCABCABCABCABCABCABC

先来解释一下其整体思路,从大的方向上来讲,该问题为三线程间的同步唤醒操作,主要的目的就是ThreadA->ThreadB->ThreadC->ThreadA……循环执行三个线程。为了控制线程执行的顺序,那么就必须要确定唤醒、等待的顺序,所以每一个线程必须同时持有两个对象锁,才能继续执行。一个对象锁是prev,就是前一个线程所持有的对象锁。还有一个就是自身对象锁。主要的思想就是,为了控制执行的顺序,必须要先持有prev锁,也就前一个线程要释放自身对象锁,再去申请自身对象锁,两者兼备时打印,之后首先调用self.notify()释放自身对象锁,唤醒下一个等待线程,再调用prev.wait()释放prev对象锁,终止当前线程,等待循环结束后再次被唤醒。运行上述代码,可以发现三个线程循环打印ABC,共10次。程序运行的主要过程就是A线程最先运行,持有C,A对象锁,后释放A,C锁,唤醒B。线程B等待A锁,再申请B锁,后打印B,再释放B,A锁,唤醒C,线程C等待B锁,再申请C锁,后打印C,再释放C,B锁,唤醒A。看起来似乎没什么问题,但如果你仔细想一下,就会发现有问题,就是初始条件,三个线程按照A,B,C的顺序来启动,按照前面的思考,A唤醒B,B唤醒C,C再唤醒A。但是这种假设依赖于JVM中线程调度、执行的顺序。

<think>我们正在讨论Java多线程中的waitnotify方法,用于线程间通信。根据引用资料,我们可以总结以下几点: 1. wait()notify()是Object类的方法,必须在同步代码块(如synchronized块)中调用。 2. 调用wait()方法会使当前线程释放锁并进入等待状态,直到其他线程调用同一对象的notify()notifyAll()方法。 3. notify()方法用于唤醒在该对象上等待一个线程(任意选择),notifyAll()唤醒所有等待线程。 4. 使用wait/notify机制可以避免轮询带来的CPU资源浪费。 下面我们通过一个示例来展示如何正确使用waitnotify实现线程间通信。</think>### Java多线程waitnotify的正确使用 在Java多线程编程中,`wait()``notify()`是实现**线程间通信**的核心机制,用于解决线程协作问题。以下是正确使用方法的详细说明: #### 1. 基本规则 - **调用位置**:必须在`synchronized`同步块或同步方法中调用 - **对象锁**:调用`wait()``notify()`的对象必须与同步锁对象一致 - **锁释放**:`wait()`会立即释放当前持有的锁 - **唤醒机制**: - `notify()`随机唤醒一个等待线程 - `notifyAll()`唤醒所有等待线程 - **状态转换**:`wait()`使线程进入`WAITING`状态,直到被唤醒 #### 2. 标准使用模板 ```java // 共享资源 class SharedResource { private boolean condition = false; public synchronized void waitForCondition() throws InterruptedException { while (!condition) { // 必须用while循环检查条件 wait(); // 释放锁并等待 } // 条件满足后执行任务 } public synchronized void setCondition() { condition = true; notifyAll(); // 唤醒所有等待线程 } } ``` #### 3. 典型生产消费模型示例 ```java class MessageQueue { private String message; private boolean empty = true; public synchronized String take() throws InterruptedException { while (empty) wait(); // 循环检测条件 empty = true; notifyAll(); // 唤醒生产者 return message; } public synchronized void put(String message) throws InterruptedException { while (!empty) wait(); // 循环检测条件 empty = false; this.message = message; notifyAll(); // 唤醒消费者 } } // 生产者线程 class Producer implements Runnable { private MessageQueue queue; public void run() { queue.put("Data"); // 生产数据 } } // 消费者线程 class Consumer implements Runnable { private MessageQueue queue; public void run() { String data = queue.take(); // 消费数据 } } ``` #### 4. 关键注意事项 1. **虚假唤醒防御** 必须用`while`循环而非`if`检查条件,防止虚假唤醒(spurious wakeup)[^5] ```java while (!condition) { // ✅ 正确做法 wait(); } ``` 2. **锁对象一致性** `wait()``notify()`必须作用于同一个对象锁: ```java synchronized (lock) { // 锁对象 lock.wait(); // ✅ 正确 // lock.notify(); } ``` 3. **优先使用notifyAll()** `notify()`可能因随机唤醒导致线程饥饿,`notifyAll()`更安全[^4] 4. **中断处理** `wait()`会抛出`InterruptedException`,需合理处理中断: ```java try { wait(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); // 恢复中断状态 } ``` #### 5. 与sleep()的区别 | 特性 | wait() | sleep() | |--------------|----------------------------------|--------------------------| | **锁释放** | 立即释放锁 | 不释放锁 | | **调用位置** | 必须在同步块内 | 任意位置 | | **唤醒方式** | 需notify()/notifyAll() | 超时自动唤醒 | | **所属类** | java.lang.Object | java.lang.Thread | #### 6. 常见错误 - **未持有锁时调用**:抛出`IllegalMonitorStateException` - **忽略中断异常**:导致线程无法响应中断 - **条件检查缺失**:未用while循环导致状态不一致 > 通过正确使用`wait()`/`notify()`机制,可有效避免轮询(busy-waiting)造成的CPU资源浪费,实现高效的线程协作[^2][^4]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值