求求你,别再用wait和notify了!(1)

简介: Condition 是 JDK 1.5 中提供的用来替代 wait 和 notify 的线程通讯方法,那么一定会有人问:为什么不能用 wait 和 notify 了? 哥们我用的好好的。老弟别着急,听我给你细说...之所以推荐使用 Condition 而非 Object 中的 wait 和 notify 的原因有两个:使用 notify 在极端环境下会造成线程“假死”;Condition 性能更高。接下来怎们就用代码和流程图的方式来演示上述的两种情况。

1.notify 线程“假死”

所谓的线程“假死”是指,在使用 notify 唤醒多个等待的线程时,却意外的唤醒了一个没有“准备好”的线程,从而导致整个程序进入了阻塞的状态不能继续执行。

以多线程编程中的经典案例生产者和消费者模型为例,我们先来演示一下线程“假死”的问题。

1.1 正常版本

在演示线程“假死”的问题之前,我们先使用 waitnotify 来实现一个简单的生产者和消费者模型,为了让代码更直观,我这里写一个超级简单的实现版本。我们先来创建一个工厂类,工厂类里面包含两个方法,一个是循环生产数据的(存入)方法,另一个是循环消费数据的(取出)方法,实现代码如下。


/**
 * 工厂类,消费者和生产者通过调用工厂类实现生产/消费
 */
class Factory {
    private int[] items = new int[1]; // 数据存储容器(为了演示方便,设置容量最多存储 1 个元素)
    private int size = 0;             // 实际存储大小
    /**
     * 生产方法
     */
    public synchronized void put() throws InterruptedException {
        // 循环生产数据
        do {
            while (size == items.length) { // 注意不能是 if 判断
                // 存储的容量已经满了,阻塞等待消费者消费之后唤醒
                System.out.println(Thread.currentThread().getName() + " 进入阻塞");
                this.wait();
                System.out.println(Thread.currentThread().getName() + " 被唤醒");
            }
            System.out.println(Thread.currentThread().getName() + " 开始工作");
            items[0] = 1; // 为了方便演示,设置固定值
            size++;
            System.out.println(Thread.currentThread().getName() + " 完成工作");
            // 当生产队列有数据之后通知唤醒消费者
            this.notify();
        } while (true);
    }
    /**
     * 消费方法
     */
    public synchronized void take() throws InterruptedException {
        // 循环消费数据
        do {
            while (size == 0) {
                // 生产者没有数据,阻塞等待
                System.out.println(Thread.currentThread().getName() + " 进入阻塞(消费者)");
                this.wait();
                System.out.println(Thread.currentThread().getName() + " 被唤醒(消费者)");
            }
            System.out.println("消费者工作~");
            size--;
            // 唤醒生产者可以添加生产了
            this.notify();
        } while (true);
    }
}


接下来我们来创建两个线程,一个是生产者调用 put 方法,另一个是消费者调用 take 方法,实现代码如下:


public class NotifyDemo {
    public static void main(String[] args) {
        // 创建工厂类
        Factory factory = new Factory();
        // 生产者
        Thread producer = new Thread(() -> {
            try {
                factory.put();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "生产者");
        producer.start();
        // 消费者
        Thread consumer = new Thread(() -> {
            try {
                factory.take();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "消费者");
        consumer.start();
    }
}


执行结果如下:


微信图片_20220120144456.jpg


从上述结果可以看出,生产者和消费者在循环交替的执行任务,场面非常和谐,是我们想要的正确结果。

1.2 线程“假死”版本

当只有一个生产者和一个消费者时,waitnotify 方法不会有任何问题,然而将生产者增加到两个时就会出现线程“假死”的问题了,程序的实现代码如下:


public class NotifyDemo {
    public static void main(String[] args) {
  // 创建工厂方法(工厂类的代码不变,这里不再复述)
        Factory factory = new Factory();
        // 生产者
        Thread producer = new Thread(() -> {
            try {
                factory.put();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "生产者");
        producer.start();
        // 生产者 2
        Thread producer2 = new Thread(() -> {
            try {
                factory.put();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "生产者2");
        producer2.start();
        // 消费者
        Thread consumer = new Thread(() -> {
            try {
                factory.take();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "消费者");
        consumer.start();
    }
}


程序执行结果如下:


微信图片_20220120144531.jpg

从以上结果可以看出,当我们将生产者的数量增加到 2 个时,就会造成线程“假死”阻塞执行的问题,当生产者 2 被唤醒又被阻塞之后,整个程序就不能继续执行了。

线程“假死”问题分析

我们先把以上程序的执行步骤标注一下,得到如下结果:


微信图片_20220120144539.jpg


从上图可以看出:当执行到第 ④ 步时,此时生产者为工作状态,而生产者 2 和消费者为等待状态,此时正确的做法应该是唤醒消费着进行消费,然后消费者消费完之后再唤醒生产者继续工作;但此时生产者却错误的唤醒了生产者 2,而生产者 2 因为队列已经满了,所以自身并不具备继续执行的能力,因此就导致了整个程序的阻塞,流程图如下所示:


微信图片_20220120144604.jpg


正确执行流程应该是这样的:


微信图片_20220120144620.jpg

相关文章
|
4月前
|
Java 程序员
从菜鸟到大神:JAVA多线程通信的wait()、notify()、notifyAll()之旅
【6月更文挑战第21天】Java多线程核心在于wait(), notify(), notifyAll(),它们用于线程间通信与同步,确保数据一致性。wait()让线程释放锁并等待,notify()唤醒一个等待线程,notifyAll()唤醒所有线程。这些方法在解决生产者-消费者问题等场景中扮演关键角色,是程序员从新手到专家进阶的必经之路。通过学习和实践,每个程序员都能在多线程编程的挑战中成长。
44 6
|
4月前
|
安全 Java
深入解读JAVA多线程:wait()、notify()、notifyAll()的奥秘
【6月更文挑战第20天】JAVA多线程中,wait(), notify(), notifyAll()是Object类的关键同步机制。wait()让线程等待并释放锁,直到被notify()或notifyAll()唤醒或超时。它们必须在同步块中使用,持有锁的线程调用。notify()唤醒一个等待线程,notifyAll()唤醒所有。最佳实践包括:与synchronized结合,循环检查条件,避免循环内notify(),通常优先使用notifyAll()。
45 0
|
4月前
|
安全 Java
JAVA多线程通信新解:wait()、notify()、notifyAll()的实用技巧
【6月更文挑战第20天】Java多线程中,`wait()`, `notify()`和`notifyAll()`用于线程通信。在生产者-消费者模型示例中,它们确保线程同步。`synchronized`保证安全,`wait()`在循环内防止虚假唤醒,`notifyAll()`避免唤醒单一线程问题。关键技巧包括:循环内调用`wait()`,优先使用`notifyAll()`以保证可靠性,以及确保线程安全和正确处理`InterruptedException`。
40 0
|
4月前
|
Java
JAVA多线程的“心灵感应”:wait()与notify()的秘密
【6月更文挑战第20天】Java多线程中,`wait()`和`notify()`是线程间协作的关键。它们充当线程间的通信桥梁,使得线程能感知对方状态。例如,生产者线程在资源满时`wait()`,消费者线程消费后`notify()`或`notifyAll()`,确保资源有效利用且避免冲突。简化的代码示例展示了这种同步机制,线程通过等待和唤醒操作实现“心灵感应”般的协同工作。
26 3
|
4月前
|
Java
wait()和notify():JAVA多线程世界的“信号兵”
【6月更文挑战第20天】在Java多线程中,`wait()`和`notify()`作为Object类的方法,扮演着线程间协调者的角色。`wait()`让线程等待并释放锁,`notify()`或`notifyAll()`唤醒等待的线程。在生产者-消费者模型中,它们用于同步访问资源,例如队列。当队列满或空时,线程调用wait()暂停,另一方完成操作后用notify()唤醒。理解并正确使用这些“信号兵”对构建高效的多线程程序至关重要。
21 0
|
4月前
|
Java 开发者
JAVA多线程通信入门:wait()、notify()、notifyAll()大揭秘!
【6月更文挑战第20天】Java多线程中,`wait()`, `notify()`, `notifyAll()`是Object类的关键通信方法。`wait()`让线程等待并释放锁,`notify()`随机唤醒一个等待的线程,`notifyAll()`唤醒所有。示例展示了在共享资源类中如何使用它们来协调生产者消费者线程。调用前需持有锁,否则抛异常。注意避免死锁和活锁,恰当使用这些方法至关重要。
33 0
|
4月前
|
Java
JAVA多线程通信:为何wait()与notify()如此重要?
【6月更文挑战第20天】Java多线程中,wait()和notify()是关键的通信工具,用于解决并发访问共享资源时的数据一致性。这些方法让线程能等待特定条件并高效唤醒,避免忙等待和响应延迟。例如,在生产者-消费者模型中,它们协调生产者和消费者的活动,当队列满或空时,线程调用wait()休眠,notifyAll()唤醒等待的线程,保证同步和资源有效利用。
24 0
|
5月前
|
Java 调度
多线程(初阶五:wait和notify)
多线程(初阶五:wait和notify)
79 0
无聊小知识.03 wait(),notify()虚假唤醒
无聊小知识.03 wait(),notify()虚假唤醒
132 0
无聊小知识.03 wait(),notify()虚假唤醒
|
存储 Java API
面试官:为什么 wait/notify 必须与 synchronized 一起使用??
面试官:为什么 wait/notify 必须与 synchronized 一起使用??
158 0
面试官:为什么 wait/notify 必须与 synchronized 一起使用??