为什么wait和notify必须放在synchronized中使用

目录
  • wait/notify基础使用
  • wait/notify和synchronized一起用?
  • 原因分析
    • wait和notify问题复现
  • 总结

前言:

在多线程编程中,wait 方法是让当前线程进入休眠状态,直到另一个线程调用了 notify 或 notifyAll 方法之后,才能继续恢复执行。而在 Java 中,wait 和 notify/notifyAll 有着一套自己的使用格式要求,也就是在使用 wait 和 notify(notifyAll 的使用和 notify 类似,所以下文就只用 notify 用来指代二者)必须配合 synchronized 一起使用才行。

wait/notify基础使用

wait 和 notify 的基础方法如下:

Object lock = new Object();
new Thread(() -> {
    synchronized (lock) {
        try {
            System.out.println("wait 之前");
            // 调用 wait 方法
            lock.wait();
            System.out.println("wait 之后");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}).start();

Thread.sleep(100);
synchronized (lock) {
    System.out.println("执行 notify");
    // 调用 notify 方法
    lock.notify();
}

以上代码的执行结果如下图所示: 

wait/notify和synchronized一起用?

那问题来了,是不是 wait 和 notify 一定要配合 synchronized 一起使用呢?wait 和 notify 单独使用行不行呢? 我们尝试将以上代码中的 synchronized 代码行删除,实现代码如下: 

初看代码好像没啥问题,编译器也没报错,好像能“正常使用”,然而当我们运行以上程序时就会发生如下错误: 

从上述结果可以看出:无论是 wait 还是 notify,如果不配合 synchronized 一起使用,在程序运行时就会报 IllegalMonitorStateException 非法的监视器状态异常,而且 notify 也不能实现程序的唤醒功能了。

原因分析

从上述的报错信息我们可以看出,JVM 在运行时会强制检查 wait 和 notify 有没有在 synchronized 代码中,如果没有的话就会报非法监视器状态异常(IllegalMonitorStateException),但这也仅仅是运行时的程序表象,那为什么 Java 要这样设计呢? 其实这样设计的原因就是为了防止多线程并发运行时,程序的执行混乱问题。初看这句话,好像是用来描述“锁”的。然而实际情况也是如此,wait 和 notify 引入锁就是来规避并发执行时程序的执行混乱问题的。那这个“执行混乱问题”到底是啥呢?接下来我们继续往下看。

wait和notify问题复现

我们假设 wait 和 notify 可以不加锁,我们用它们来实现一个自定义阻塞队列。 这里的阻塞队列是指读操作阻塞,也就是当读取数据时,如果有数据就返回数据,如果没有数据则阻塞等待数据,实现代码如下:

class MyBlockingQueue {
    // 用来保存数据的集合
    Queue<String> queue = new LinkedList<>();

    /**
     * 添加方法
     */
    public void put(String data) {
        // 队列加入数据
        queue.add(data);
        // 唤醒线程继续执行(这里的线程指的是执行 take 方法的线程)
        notify(); // ③
    }

    /**
     * 获取方法(阻塞式执行)
     * 如果队列里面有数据则返回数据,如果没有数据就阻塞等待数据
     * @return
     */
    public String take() throws InterruptedException {
        // 使用 while 判断是否有数据(这里使用 while 而非 if 是为了防止虚假唤醒)
        while (queue.isEmpty()) { // ①
            // 没有任务,先阻塞等待
            wait(); // ②
        }
        return queue.remove(); // 返回数据
    }
}

注意上述代码,我们在代码中标识了三个关键执行步骤:①:判断队列中是否有数据;②:执行 wait 休眠操作;③:给队列中添加数据并唤醒阻塞线程。 如果不强制要求添加 synchronized那么就会出现如下问题:

步骤 线程1 线程2
1 执行步骤 ① 判断当前队列中没有数据  
2   执行步骤 ③ 将数据添加到队列,并唤醒线程1继续执行
3 执行步骤 ② 线程 1 进入休眠状态  

从上述执行流程看出问题了吗? 如果 wait 和 notify 不强制要求加锁,那么在线程 1 执行完判断之后,尚未执行休眠之前,此时另一个线程添加数据到队列中。然而这时线程 1 已经执行过判断了,所以就会直接进入休眠状态,从而导致队列中的那条数据永久性不能被读取,这就是程序并发运行时“执行结果混乱”的问题。 然而如果配合 synchronized 一起使用的话,代码就会变成以下这样:

class MyBlockingQueue {
    // 用来保存任务的集合
    Queue<String> queue = new LinkedList<>();

    /**
     * 添加方法
     */
    public void put(String data) {
        synchronized (MyBlockingQueue.class) {
            // 队列加入数据
            queue.add(data);
            // 为了防止 take 方法阻塞休眠,这里需要调用唤醒方法 notify
            notify(); // ③
        }
    }

    /**
     * 获取方法(阻塞式执行)
     * 如果队列里面有数据则返回数据,如果没有数据就阻塞等待数据
     * @return
     */
    public String take() throws InterruptedException {
        synchronized (MyBlockingQueue.class) {
            // 使用 while 判断是否有数据(这里使用 while 而非 if 是为了防止虚假唤醒)
            while (queue.isEmpty()) {  // ①
                // 没有任务,先阻塞等待
                wait(); // ②
            }
        }
        return queue.remove(); // 返回数据
    }
}

这样改造之后,关键步骤 ① 和关键步骤 ② 就可以一起执行了,从而当线程执行了步骤 ③ 之后,线程 1 就可以读取到队列中的那条数据了,它们的执行流程如下:

步骤 线程1 线程2
1 执行步骤 ① 判断当前队列没有数据  
2 执行步骤 ② 线程进入休眠状态  
3   执行步骤 ③ 将数据添加到队列,并执行唤醒操作
4 线程被唤醒,继续执行  
5 判断队列中有数据,返回数据  

这样咱们的程序就可以正常执行了,这就是为什么 Java 设计一定要让 wait 和 notify 配合上 synchronized 一起使用的原因了。

总结

本文介绍了 wait 和 notify 的基础使用,以及为什么 wait 和 notify/notifyAll 一定要配合 synchronized 使用的原因。如果 wait 和 notify/notifyAll 不强制和 synchronized 一起使用,那么在多线程执行时,就会出现 wait 执行了一半,然后又执行了添加数据和 notify 的操作,从而导致线程一直休眠的缺陷。​

到此这篇关于为什么wait和notify必须放在synchronized中使用的文章就介绍到这了,更多相关wait与notify的使用内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Java多线程通信wait()和notify()代码实例

    1.wait()方法和sleep()方法: wait()方法在等待中释放锁:sleep()在等待的时候不会释放锁,抱着锁睡眠. 2.notify(): 随机唤醒一个线程,将等待队列中的一个等待线程从等待队列中移到同步队列中. 代码如下 public class Demo_Print { public static void main(String[] args) { Print p = new Print(); new Thread() { public void run() { while (

  • 基于线程的wait和notify使用,生产消费案例

    目录 案例图解 生产方逻辑图 消费方逻辑图 代码实现 多个线程可以相互竞争,也可以互相协作完成一件事情. Object的相关方法 Object相关方法 描述 void wait() 让当前线程等待,如果没有被唤醒,就一直等待 void wait(long timeout) 让当前线程等待指定毫秒值,如果到了指定的毫秒值自动唤醒 void notify() 唤醒一个线程,唤醒的是当前对象锁下的一个线程 void notifyAll() 唤醒所有线程,唤醒的是当前对象锁下面的所有线程 这些方法一定要

  • java wait()/notify() 实现生产者消费者模式详解

    java wait()/notify() 实现生产者消费者模式 java中的多线程会涉及到线程间通信,常见的线程通信方式,例如共享变量.管道流等,这里我们要实现生产者消费者模式,也需要涉及到线程通信,不过这里我们用到了java中的wait().notify()方法: wait():进入临界区的线程在运行到一部分后,发现进行后面的任务所需的资源还没有准备充分,所以调用wait()方法,让线程阻塞,等待资源,同时释放临界区的锁,此时线程的状态也从RUNNABLE状态变为WAITING状态: noti

  • Java object wait notify notifyAll代码解析

    测试代码: public static Object loc=new Object(); public static void main(String[] args) throws Exception{ Thread t1=new Thread(new Runnable() { @Override public void run() { try { java.text.SimpleDateFormat sdf=new SimpleDateFormat("HH:mm:ss SSS");

  • Object类wait及notify方法原理实例解析

    Object类中的wait和notify方法(生产者和消费者模式) 不是通过线程调用 wait(): 让正在当前对象上活动的线程进入等待状态,无期限等待,直到被唤醒为止 notify(): 让正在当前对象上等待的线程唤醒 notifyAll(): 唤醒当前对象上处于等待的所有线程 生产者和消费者模式 生产线程和消费线程达到均衡 wait方法和notify方法建立在synchronized线程同步的基础之上 wait方法: 释放当前对象占有的锁 notify方法: 通知,不会释放锁 实现生产者和消

  • Java如何正确的使用wait-notify方法你知道吗

    目录 1.sleep(longn)和wait(longn)的区别 2.正确使用wait-notify方法[while(条件)+wait] 2.1问题1 2.2问题2 2.3问题3 2.4问题4 2.5最终结果 总结 问题: sleep() 和 wait() 有什么区别? 1. sleep(long n) 和 wait(long n) 的区别 (1) sleep 是 Thread 方法,而 wait 是 Object 的方法 ; (2) sleep 不需要强制和 synchronized 配合使用

  • 分析java并发中的wait notify notifyAll

    一.前言 java 面试是否有被问到过,sleep 和 wait 方法的区别,关于这个问题其实不用多说,大多数人都能回答出最主要的两点区别: sleep 是线程的方法, wait / notify / notifyAll 是 Object 类的方法: sleep 不会释放当前线程持有的锁,到时间后程序会继续执行,wait 会释放线程持有的锁并挂起,直到通过 notify 或者 notifyAll 重新获得锁. 另外还有一些参数.异常等区别,不细说了.本文重点记录一下 wait / notify

  • 聊聊Object类中的wait()和notify()方法

    目录 Object类中的wait()和notify()方法 一.特点 二.wait()方法的作用 三.notify()方法的作用 四.wait()和notify()的使用 object中的wait和notify细节 wait jdk中为啥要规定我们要使用while,而不是if? Object类中的wait()和notify()方法 一.特点 wait()和notify()方法并不是线程对象的方法,是Java中任何一个Java对象都有的方法,并不特殊. 二.wait()方法的作用 Object o

  • Java多线程中的wait/notify通信模式实例详解

    前言 最近在看一些JUC下的源码,更加意识到想要学好Java多线程,基础是关键,比如想要学好ReentranLock源码,就得掌握好AQS源码,而AQS源码中又有很多Java多线程经典的一些应用:再比如看了线程池的核心源码实现,又学到了很多核心实现,其实这些都可以提出来慢慢消化并变成自己的知识点,今天这个Java等待/通知模式其实是Thread.join()实现的关键,还有线程池工作线程中线程跟线程之间的通信的核心所在,故在此为了加深理解,做此记录! 参考资料<Java并发编程艺术>(电子PD

  • 为什么wait和notify必须放在synchronized中使用

    目录 wait/notify基础使用 wait/notify和synchronized一起用? 原因分析 wait和notify问题复现 总结 前言: 在多线程编程中,wait 方法是让当前线程进入休眠状态,直到另一个线程调用了 notify 或 notifyAll 方法之后,才能继续恢复执行.而在 Java 中,wait 和 notify/notifyAll 有着一套自己的使用格式要求,也就是在使用 wait 和 notify(notifyAll 的使用和 notify 类似,所以下文就只用

  • Android植物大战僵尸小游戏

    Android植物大战僵尸小游戏全部内容如下: 相关下载:Android植物大战僵尸小游戏 具体代码如下所示: package com.example.liu.mygame; import com.example.liu.mygame.global.Config; import com.example.liu.mygame.tools.DeviceTools; import com.example.liu.mygame.view.GameView; import android.os.Bundl

  • 详解Java中的锁Lock和synchronized

    一.Lock接口 1.Lock接口和synchronized内置锁 a)synchronized:Java提供的内置锁机制,Java中的每个对象都可以用作一个实现同步的锁(内置锁或者监视器Monitor),线程在进入同步代码块之前需要或者这把锁,在退出同步代码块会释放锁.而synchronized这种内置锁实际上是互斥的,即没把锁最多只能由一个线程持有. b)Lock接口:Lock接口提供了与synchronized相似的同步功能,和synchronized(隐式的获取和释放锁,主要体现在线程进

  • 详解Java中synchronized关键字的死锁和内存占用问题

    先看一段synchronized 的详解: synchronized 是 java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码. 一.当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程得到执行.另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块. 二.然而,当一个线程访问object的一个synchronized(this)同步代码块时,另一个线程仍然可以

  • Java 中Object的wait() notify() notifyAll()方法使用

    Java 中Object的wait() notify() notifyAll()方法使用 一.前言 对于并发编程而言,除了Thread以外,对Object对象的wati和notify对象也应该深入了解其用法,虽然知识点不多. 二.线程安全基本知识 首先应该记住以下基本点,先背下来也无妨: 同一时间一个锁只能被一个线程持有 调用对象的wait()和notify()前必须持有它 三.wait()和notify()理解 3.1 wait()和notify()方法简介 wait()和notify()都是

  • Java多线程中wait、notify、notifyAll使用详解

    基础知识 首先我们需要知道,这几个都是Object对象的方法.换言之,Java中所有的对象都有这些方法. public final native void notify(); public final native void notifyAll(); public final native void wait(long timeout) throws InterruptedException; public final void wait() throws InterruptedExceptio

  • 实例解析Java中的synchronized关键字与线程安全问题

    首先来回顾一下synchronized的基本使用: synchronized代码块,被修饰的代码成为同步语句块,其作用的范围是调用这个代码块的对象,我们在用synchronized关键字的时候,能缩小代码段的范围就尽量缩小,能在代码段上加同步就不要再整个方法上加同步.这叫减小锁的粒度,使代码更大程度的并发. synchronized方法,被修饰的方法成为同步方法,其作用范围是整个方法,作用对象是调用这个方法的对象. synchronized静态方法,修饰一个static静态方法,其作用范围是整个

  • Java中synchronized 的4个优化技巧

    目录 前言 1.锁膨胀 2.锁消除 3.锁粗化 4.自适应自旋锁 总结 前言 synchronized 在 JDK 1.5 时性能是比较低的,然而在后续的版本中经过各种优化迭代,它的性能也得到了前所未有的提升,上一篇文章我们谈到了锁膨胀对 synchronized 性能的提升,然而它也只是“众多” synchronized 性能优化方案中的一种,那么我们本文就来盘点一下 synchronized 的核心优化方案. synchronized 核心优化方案主要包含以下 4 个: 锁膨胀 锁消除 锁粗

随机推荐