什么是Java虚假唤醒及如何避免虚假唤醒?《多线程学习之十四》

本文深入探讨了在多线程环境下如何避免假唤醒现象,通过具体示例解释了将wait操作置于while循环中以防止虚假唤醒的原因。文章提供了Java代码示例,展示如何在生产者-消费者模式中正确使用synchronized关键字和wait/notify机制。

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

那什么是假唤醒?
当一个条件满足时,很多线程都被唤醒了,但是只有其中部分是有用的唤醒,其它的唤醒都是无用功
1.比如说买货,如果商品本来没有货物,突然进了一件商品,这是所有的线程都被唤醒了
	,但是只能一个人买,所以其他人都是假唤醒,获取不到对象的锁
避免虚假唤醒

下面是避免虚假唤醒的示例:

package duoxiancheng.bao;

/*
 * 虚假唤醒的解决:
 *  wait要始终保证在while循环当中。
 */
public class LockTest {
    public static void main(String[] args) {
        Clerk clerk = new Clerk();
        Producter producter = new Producter(clerk);
        Customer customer = new Customer(clerk);

        new Thread(producter,"生产者A").start();
        new Thread(customer,"消费者A").start();
        new Thread(producter,"生产者B").start();
        new Thread(customer,"消费者B").start();
    }
}

// 售货员
class Clerk {
    private int product = 0;

    // 进货
    public synchronized void add() {
        // 产品已满
        while (product >=1) {
            System.out.println(Thread.currentThread().getName() + ": " + "已满!");
            try {
                this.wait();
            } catch (InterruptedException e) {
            }
        }
        ++product;
        // 该线程从while中出来的时候,是满足条件的
        System.out.println(Thread.currentThread().getName() + ": " +"....................进货成功,剩下"+product);
        this.notifyAll();
    }

    // 卖货
    public synchronized void sale() {
        while (product <=0) {
            System.out.println(Thread.currentThread().getName() + ": " + "没有买到货");
            try {
                this.wait();
            } catch (InterruptedException e) {
            }
        }
        --product;
        System.out.println(Thread.currentThread().getName() + ":买到了货物,剩下 " + product);
        this.notifyAll();
    }
}

// 生产者
class Producter implements Runnable {
    private Clerk clerk;

    public Producter(Clerk clerk) {
        this.clerk = clerk;
    }

    // 进货
    @Override
    public void run() {
        for(int i = 0; i < 20; ++i) {
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
            }
            clerk.add();
        }
    }
}

// 消费者
class Customer implements Runnable {
    private Clerk clerk;
    public Customer(Clerk clerk) {
        this.clerk = clerk;
    }

    // 买货
    @Override
    public void run() {
        for(int i = 0; i < 20; ++i) {
            clerk.sale();
        }
    }
}

怎么产生虚假唤醒
把  while (product >=1) {}
换成   if (product >=1) {}
就会出现虚假唤醒
为什么 if会出现虚假唤醒
因为if只会执行一次,执行完会接着向下执行if()外边的
而while不会,直到条件满足才会向下执行while()外边的
<think>好的,我现在需要解释Java中线程虚假唤醒的概念。首先,用户可能不太了解多线程编程,所以我得从基础讲起。虚假唤醒这个词听起来有点抽象,得用简单的例子说明。 我记得虚假唤醒是指在多线程环境下,线程可能在未被明确通知的情况下被唤醒。比如,当使用wait()和notify()时,即使没有其他线程调用notify,线程也可能自己醒过来。这会导致程序逻辑错误,比如条件还没满足就继续执行了。 接下来要结合用户的参考引用。引用[1]提到,虚假唤醒可能由操作系统或JVM实现引起,程序员需要处理这种情况。引用[2]和[3]也强调了正确使用循环判断条件的重要性。引用[4]则指出使用while而不是if来避免问题。 我需要解释为什么用while而不是if。例如,当多个线程被notifyAll唤醒时,可能只有部分满足条件,用while可以让线程再次检查条件,避免虚假唤醒后直接执行后续代码。 还要给出示例代码,展示正确的处理方式。比如在条件判断中使用while循环,并处理InterruptedException。这样用户能直观看到如何实现。 最后,生成相关问题,帮助用户深入理解。比如如何避免虚假唤醒,与信号量机制的关系,应用场景等。 总结步骤:定义虚假唤醒、原因、解决办法、代码示例、相关问题。确保引用正确,比如[^1]到[^4]对应提供的参考内容。语言要简洁明了,避免技术术语过多,让新手也能理解。</think>### Java 线程虚假唤醒的概念解释 **虚假唤醒(Spurious Wakeup)** 是多线程编程中的一种现象,指线程在调用 `wait()` 方法后,可能未通过 `notify()` 或 `notifyAll()` 的显式通知就被唤醒,且此时线程等待的条件尚未满足。这种现象可能由操作系统或 Java 虚拟机(JVM)的底层实现导致。 #### 核心原因 1. **操作系统/JVM 的调度机制**:某些系统实现可能因优化或信号处理问题,允许线程在未收到通知时自行唤醒。 2. **多线程竞争条件**:当多个线程通过 `notifyAll()` 被唤醒时,某些线程可能因竞争资源失败而需要重新进入等待状态。 #### 如何避免虚假唤醒? 正确的做法是**在 `wait()` 方法外层使用 `while` 循环而非 `if` 判断**。例如: ```java synchronized (lock) { while (conditionIsNotMet) { // 使用 while 循环而非 if lock.wait(); } // 执行后续操作 } ``` - **`while` 的作用**:即使线程被虚假唤醒,循环会再次检查条件是否满足,若不满足则重新进入等待状态。 - **`if` 的风险**:若使用 `if`,虚假唤醒可能导致线程跳过条件检查,直接执行后续逻辑,引发数据不一致或程序错误[^2]。 #### 典型场景示例 假设一个生产者-消费者模型中,消费者线程检查缓冲区是否为空: ```java // 错误方式:使用 if synchronized (buffer) { if (buffer.isEmpty()) { buffer.wait(); } consume(buffer.remove()); } // 正确方式:使用 while synchronized (buffer) { while (buffer.isEmpty()) { // 防止虚假唤醒 buffer.wait(); } consume(buffer.remove()); } ``` 在错误示例中,若消费者因虚假唤醒直接执行 `consume()`,可能操作空缓冲区导致异常;而正确示例通过 `while` 循环规避了这一问题。 #### 总结 - **根本原则**:始终假设虚假唤醒可能发生,并通过循环条件检查确保线程安全[^3]。 - **规范实践**:Java 官方文档明确建议将 `wait()` 调用包裹在循环中。 ---
评论 16
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值