【JUC基础】07. wait(),notify()虚假唤醒

文章通过示例代码解释了Java中Object类的wait()和notify()方法在多线程环境中的作用,展示了它们如何用于线程间的通信和同步。在初始示例中,两个线程交替执行add()和sub()操作。然而,当增加更多线程时,由于虚假唤醒的问题,导致预期结果出错。为解决此问题,文章引入了while循环来确保线程在条件满足时才继续执行,从而保证了正确性。

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

1、什么是wait和notify

属于Object类的两个方法。

wait(),使当前线程等待,直到另一个线程使用notify()或notifyAll()方法唤醒。

notify(),唤醒正在wait等待的线程。

官方API文档说明:

2、 示例代码

既然说明了wait()和notify()的功能,那么我们手撸一段代码来试下:

/**
 * @author Shamee loop
 * @date 2023/4/9
 */
public class ObjectWaitNotifyDemo {

    public static void main(String[] args) {
        NumberOper object = new NumberOper();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                object.add();
            }
        }, "thread-add-1").start();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                object.sub();
            }
        }, "thread-sub-1").start();
    }
}

/**
 * 定义一个数量操作类
 * 其中包含两个方法,
 * 一个是对临界值number++;当判断number!=0,就等待number变成0后在执行。同时唤醒另一个线程
 * 另一个方法是对临界值number--;当判断number==0,就等待number!=0后在执行。同时唤醒另一个线程
 * 同时两个方法分别加了synchronized锁,确保线程安全问题
 */
class NumberOper {
    private int number = 0;

    public synchronized void add() {
        if(number != 0) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        number++;
        System.out.println("线程" + Thread.currentThread().getName() + "执行了add(),number====>" + number);
        this.notifyAll();
    }

    public synchronized void sub() {
        if(number == 0) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        number--;
        System.out.println("线程" + Thread.currentThread().getName() + "执行了sub(),number====>" + number);
        this.notifyAll();
    }

}

预期的结果应该是:

线程tread-add-1执行,判断到number==0,就number++;这时候控制台打印number==>1

线程tread-sub-1执行,判断到number==0,则进行等待;等到tread-add-1执行完毕后,唤醒了tread-sub-1执行,这时候tread-sub-1就执行了number--操作;这时候控制台打印number==>0

实际执行结果:

 果然是这样。那么这篇文章就结束了吗?

3、多个线程执行

上面操作示例,只开启了2个线程。但如果开启多个线程呢?我们再试下:

/**
 * @author Shamee loop
 * @date 2023/4/9
 */
public class ObjectWaitNotifyDemo {

    public static void main(String[] args) {
        NumberOper object = new NumberOper();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                object.add();
            }
        }, "thread-add-1").start();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                object.sub();
            }
        }, "thread-sub-1").start();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                object.add();
            }
        }, "thread-add-2").start();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                object.sub();
            }
        }, "thread-sub-2").start();
    }
}

/**
 * 定义一个数量操作类
 * 其中包含两个方法,
 * 一个是对临界值number++;当判断number!=0,就等待number变成0后在执行。同时唤醒另一个线程
 * 另一个方法是对临界值number--;当判断number==0,就等待number!=0后在执行。同时唤醒另一个线程
 * 同时两个方法分别加了synchronized锁,确保线程安全问题
 */
class NumberOper {
    private int number = 0;

    public synchronized void add() {
        if(number != 0) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        number++;
        System.out.println("线程" + Thread.currentThread().getName() + "执行了add(),number====>" + number);
        this.notifyAll();
    }

    public synchronized void sub() {
        if(number == 0) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        number--;
        System.out.println("线程" + Thread.currentThread().getName() + "执行了sub(),number====>" + number);
        this.notifyAll();
    }

}

预期的结果应该是:

线程tread-add-1执行,判断到number==0,就number++;这时候控制台打印number==>1

线程tread-sub-1执行,判断到number==0,则进行等待;等到tread-add-1执行完毕后,唤醒了tread-sub-1执行,这时候tread-sub-1就执行了number--操作;这时候控制台打印number==>0

实际执行结果:

 很明显结果不对了,甚至出现了负数等值。这是为啥?

4、虚假唤醒

我们来看下官方API文档怎么说:

而文档给出来的建议是,临界值的判断使用while。我们再试下:

public synchronized void add() {
        while(number != 0) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        number++;
        System.out.println("线程" + Thread.currentThread().getName() + "执行了add(),number====>" + number);
        this.notifyAll();
    }

    public synchronized void sub() {
        while(number == 0) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        number--;
        System.out.println("线程" + Thread.currentThread().getName() + "执行了sub(),number====>" + number);
        this.notifyAll();
    }

执行结果:

 似乎一切正常了。那么是为什么呢?用if不行用while就可以。

什么是虚假唤醒:是不想唤醒它或者说不确定是否应该唤醒,但是被唤醒了。对程序来说,wait 方法应该卡住当前程序,不应该往后执行;但是实际上并没有被卡住,而是在非预期的时间程序正常执行了,没有程序没有被卡住就是被虚假唤醒了

5、结论

在if条件判断下,他只会判断一次,如果这时候被notify唤醒之后,程序会从当前等待的代码继续执行(也就是进入了if判断之后的代码)。而如果使用while判断,唤醒后会重新执行while循环条件。如果条件成立,就继续wait。因此就出现了上面看到的现象。

好了,又白嫖了一个无聊的小知识,没用但有趣的小知识。

<think>好的,用户想了解Java中LockSupport和Object.wait的区别、关系以及使用场景。首先,我需要回忆这两个机制的基本概念。LockSupport是JUC包中的工具类,提供park和unpark方法用于线程阻塞和唤醒。而Object.waitnotify是Object类的方法,用于传统的线程间通信,必须在同步块中使用。 接下来,要比较它们的区别。首先想到的是使用方式的不同:Object.wait需要在synchronized块内获取对象锁,否则会抛出IllegalMonitorStateException,而LockSupport.park不需要,直接调用即可。然后是唤醒机制,wait需要notifynotifyAll来唤醒,而LockSupport.unpark可以指定线程提前唤醒,或者之后解除阻塞。还有许可证机制,LockSupport的unpark会给线程一个许可证,即使之后调用park也不会阻塞,这点和wait不同,后者没有这种累积效应。 再者,需要考虑它们的使用场景。Object.wait适用于经典的等待-通知模式,比如生产者消费者问题。而LockSupport更适合更底层的线程控制,比如在AQS(AbstractQueuedSynchronizer)中的使用,或者需要更灵活的线程挂起和恢复的场景。 还要提到中断处理。两者对中断的响应不同,park会立即返回并清除中断状态,而wait被中断时会抛出InterruptedException,需要捕获处理。 另外,需要引用提供的参考资料来支持这些观点。例如,引用[4]指出wait必须在synchronized中使用,否则会抛出异常。引用[5]提到三种让线程休眠的方法,包括LockSupport.park。引用[3]和[4]讨论了LockSupport与wait的区别,比如许可证机制和同步要求。 最后,应该总结它们的适用情况,帮助用户根据具体需求选择合适的方法。可能用户在实际开发中遇到了线程同步的问题,或者在阅读源码时看到这两种方法的使用,需要理解其差异。需要确保回答结构清晰,分点说明,并结合示例代码帮助理解。</think>### Java中LockSupport与Object.wait的关系及区别分析 #### 一、核心关系 两者均为Java线程同步机制,但属于不同层级的解决方案: - $Object.wait()$ 是Java基础API,需要配合$synchronized$关键字使用[^4] - $LockSupport$ 是JUC包中的高级工具,提供更底层的线程控制能力[^3] #### 二、主要差异对比(表格形式) | 特性 | LockSupport | Object.wait/notify | |---------------------|----------------------------|----------------------------| | 同步机制 | 无锁机制 | 必须持有对象监视器锁[^4] | | 唤醒顺序 | 精准唤醒指定线程 | 随机唤醒等待队列中的线程 | | 许可证机制 | 支持许可凭证累积(unpark) | 无类似机制 | | 异常处理 | 无检查异常 | 需处理InterruptedException| | 使用场景 | 并发框架底层实现 | 常规线程间通信 | #### 三、实现原理详解 1. **Object.wait() 工作流程**: ```java synchronized(obj) { // 必须获取对象锁[^2] while(condition) { obj.wait(); // 释放锁并进入等待队列 } // 被唤醒后自动重新获取锁 } ``` 2. **LockSupport 工作机制**: ```java LockSupport.unpark(targetThread); // 先发放许可 LockSupport.park(); // 有许可立即返回,无许可阻塞[^3] ``` #### 四、典型应用场景 1. **Object.wait适用场景**: - 生产者消费者模式 - 条件变量等待(需配合synchronized使用) - 经典线程间通信场景 2. **LockSupport优势场景**: - 实现高级同步器(如AQS) - 需要先唤醒后阻塞的特殊逻辑 - 避免锁竞争引发的性能问题 - 精确控制特定线程状态 #### 五、中断处理差异 - $LockSupport.park()$响应中断时**直接返回**,需通过$Thread.interrupted()$检测中断状态[^5] - $Object.wait()$被中断会**抛出InterruptedException**,需通过try-catch处理[^1] #### 六、注意事项 1. 错误使用示例: ```java Object o = new Object(); o.wait(); // 抛出IllegalMonitorStateException[^2] ``` 2. 正确配合使用方式: ```java Thread target = new Thread(() -> { LockSupport.park(); System.out.println("被唤醒"); }); target.start(); LockSupport.unpark(target); // 提前发放许可保证立即执行 ```
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

有一只柴犬

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

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

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

打赏作者

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

抵扣说明:

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

余额充值