<JavaEE> 多线程编程中的“等待和通知机制”:wait 和 notify 方法

目录

一、等待和通知机制的概念

[二、wait() 方法](#二、wait() 方法)

[2.1 wait() 方法的使用](#2.1 wait() 方法的使用)

[2.2 超时等待](#2.2 超时等待)

[2.3 异常唤醒](#2.3 异常唤醒)

[2.4 唤醒等待的方法](#2.4 唤醒等待的方法)

[三、notify() 方法](#三、notify() 方法)

[四、notifyAll() 方法](#四、notifyAll() 方法)

[五、wait 和 sleep 的对比](#五、wait 和 sleep 的对比)


一、等待和通知机制的概念

|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 1)什么是等待和通知机制? |
| 线程是抢占式执行的,无法预知线程之间的执行顺序。 但有时程序员也希望能合理协调多个线程的执行顺序。 因此,在 Java 中使用了等待(wait)和通知(notify)机制,用于在应用层面上干预多个线程的执行顺序。 应当注意的是,干预执行顺序并不是干预系统的线程调度策略(操作系统内核中的线程调度仍是无序的),而是使被指定的线程,主动放弃被系统调度的机会,直到其他线程对被指定的线程发出通知,这个线程才再次参与系统的线程调度。(排队都不排了,自然也就轮不到它了) |

|-----------------|----------------------|
| 2)使用等待和通知机制主要涉及以下三个方法 ||
| wait() | 让线程进入等待状态。 |
| notify() | 唤醒在当前对象上等待的一个线程。 |
| notifyAll() | 唤醒在当前对象上等待的所有线程。 |
| 以上三个方法都是 Object 类的方法。 ||


二、wait() 方法

2.1 wait() 方法的使用

|-----------------------------------------------------------------------------------|
| 1)wait() 方法需要配合 synchronized 关键字使用 |
| wait() 方法必须在 synchronized 修饰的代码块或方法中使用,否则会抛出 IllegalMonitorStateException 异常。 |

|------------------------------------------------------------------------------------------------------------------|
| 2)使用锁对象调用 wait() 方法 |
| 虽然,wait() 是 Object 类的方法,任何对象都可以调用该方法。 但是为了实现等待通知机制,要求调用 wait() 的对象必须是锁对象,且这个锁对象要与 synchronized 指定的锁对象一致。 |

|-------|---------------------------|
| 3)wait() 方法具体做了什么? ||
| wait() 方法主要执行了以下三个操作: ||
| <1> | 释放当前的锁。 |
| <2> | 使当前线程进入等待队列。 |
| <3> | 通过某些条件被唤醒时,重新尝试获取当前锁。 |

++代码演示wait()使用方法和使用结果:++

java 复制代码
    public static void main(String[] args) throws InterruptedException {
        //创建锁对象;
        Object locker = new Object();
        
        System.out.println("wait前");
        //在 synchronized 代码块中调用 wait 方法;
        synchronized (locker){
            // wait 方法是由锁对象调用的,调用后,线程释放锁,进入等待队列;
            locker.wait();
        }
        System.out.println("wait后");
    }

//运行结果:
wait前
...

程序没有执行完毕,线程一直在 wait 。

2.2 超时等待

|------------------------------------------------------------------------------------------------------------------------------------------------------|
| 有时间限制的等待 |
| 上文中的代码,除非有其他线程唤醒,否则执行后会一直处于 wait 的状态,这就使得程序陷入了"停摆"状态。 在部分场景中,我们可以使用带时间参数的 wait() 方法来规避这个问题。即使没有其他线程唤醒 wait ,wait 仍会在超过规定时间后,自动唤醒,避免了程序的"停摆"。 |

++代码演示带时间参数的wait()使用方法和使用结果:++

java 复制代码
    public static void main(String[] args) throws InterruptedException {
        //创建锁对象;
        Object locker = new Object();

        System.out.println("wait前");
        //在 synchronized 代码块中调用 wait 方法;
        synchronized (locker){
            // wait 方法是由锁对象调用的,调用后,线程释放锁,进入等待队列;
            locker.wait(3000);
            //3秒后线程被唤醒;
        }
        System.out.println("wait后");
    }

//运行结果:
wait前
wait后

成功执行完毕。

2.3 异常唤醒

++代码演示通过抛出异常唤醒wait:++

java 复制代码
    public static void main(String[] args) {
        //创建锁对象;
        Object locker = new Object();

        Thread t1 = new Thread(()->{
            System.out.println("wait前");
            //在 synchronized 代码块中调用 wait 方法;
            synchronized (locker){
                // wait 方法是由锁对象调用的,调用后,线程释放锁,进入等待队列;
                try {
                    locker.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //3秒后线程被唤醒;
            }
            System.out.println("wait后");
        });

        t1.start();

        //抛出异常,清除中断标志,唤醒t1线程。
        t1.interrupt();
    }

//运行结果:
wait前
wait后
java.lang.InterruptedException

抛出了InterruptedException后,线程被唤醒,继续执行。

2.4 唤醒等待的方法

|-------|--------------------------------------------------|
| 唤醒等待的方法有三种: ||
| <1> | 超时等待:超过了 wait() 方法指定的等待时间。 |
| <2> | 异常唤醒:通过其他线程调用该等待线程的 interrupted() 方法,抛出异常唤醒。 |
| <3> | notify() 方法:其他线程调用该对象的 notify() 方法。 |


三、notify() 方法

|---------------------------|
| 1)notify() 方法有什么作用? |
| notify() 方法可以唤醒等待的线程。 |

|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 2)notify() 也要写在 synchronized 修饰的代码块或方法中 |
| notify() 方法也是 Object 类的方法,所以任何对象都可以调用。 在操作系统的原生 API 中,也有 wait() 和 notify() 方法。与 wait() 方法不同,操作系统的原生 API 没有要求 notify() 必须在 synchronized 修饰的代码块或方法中使用。 但是,应注意,在 Java 中还是特别约定了 notify() 方法也是要放在 synchronized 修饰的代码块或方法中的。 |

|-------------------------------------------------------------------------------------|
| 3)wait() 和 notify() 方法是通过锁对象联系的 |
| 一个锁对象调用的 wait() 只能被同一个锁对象调用的 notify() 唤醒。 如果唤醒时,同一个锁对象有多个线程正在等待,此时只会随机唤醒一个。 |

|------------------------------------------------------------------------------------------------------------------------------------------------------|
| 4)执行 notify() 方法后,锁在什么时候释放? |
| 在 notify() 方法后,当前线程不会马上释放锁对象,而是等到 线程 执行完 notify() 方法所在的代码块或方法后,才会释放锁对象。 这也是 Java 中 约定 notify() 方法要放在 synchronized 修饰的代码块或方法中的原因。 |

++代码演示通过 notify 唤醒wait:++

java 复制代码
    public static void main(String[] args) throws InterruptedException {
        //创建一个锁对象;
        Object locker = new Object();

        //创建一个线程;
        Thread t1 = new Thread(()->{
            System.out.println("wait前");
            //打印"wait前"后等待;
            synchronized (locker){
                try {
                    locker.wait();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            //被唤醒后打印"wait后";
            System.out.println("wait后");
        });
        t1.start();

        //休眠两秒,保证t1线程进入等待状态。
        Thread.sleep(2000);
        //打印"notify前"后唤醒;
        System.out.println("notify前");
        synchronized (locker){
            locker.notify();
            //在出代码块前,打印"notify后"。
            System.out.println("notify后");
        }
    }

//运行结果:
wait前
notify前
notify后
wait后

打印"wait前"之后进入阻塞等待,直到被notify唤醒之后才打印了"wait后"。

四、notifyAll() 方法

|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| notifyAll() 方法有什么作用? |
| 唤醒这个锁对象上所有等待的线程。 有多个线程使用同一个锁对象 wait ,当对这个锁对象使用 notifyALL() 方法时,所有在等待的线程都会唤醒。 但是需要注意,在唤醒之后,由于需要重新获取锁,此时被唤醒的线程必然要进行锁竞争,所以这些被唤醒的线程并不是同时就开始执行各自的代码了,而仍然是有先后顺序的执行,顺序依旧是随机的。 |


五、wait 和 sleep 的对比

|----------------|
| 相同点 |
| 都会使线程阻塞等待。 |

|---------|---------------------------------------------------------|--------------------------------------------------|
| 不同点 | wait() 方法 | sleep() 方法 |
| 用途 | 用于线程间通信。 | 用于线程阻塞等待。 |
| 用法 | 是 Object 类中的方法, 需要在被 synchronized 修饰的代码块或方法中使用。 | 是 Tread 类中的静态方法, 方法的使用与 synchronized 无关。 |
| 状态 | 被调用后,当前线程进入 BLOCK 状态并释放锁。 | 被调用后,当前线程进入 TIME_WAIT 状态。 |
| 唤醒 | 通常通过 notify 唤醒; 可以通过超时或抛出异常唤醒; | 通常按设定的时间唤醒; 可以通过抛出异常唤醒; |


阅读指针 -> 《经典设计模式之 -- 单例模式("饿汉模式"和"懒汉模式"实现单例模式)》

<JavaEE> 单例模式的两种实现:"饿汉模式"和"懒汉模式"-CSDN博客介绍了什么是单例模式和单例模式的两种实现模式。重点介绍了单例模式中,"懒汉模式"在多线程下的实现。https://blog.csdn.net/zzy734437202/article/details/134785459

相关推荐
robin_suli3 小时前
Java多线程八股(三)一>多线程环境使用哈希表和ArrayList
java·开发语言·多线程·哈希表
hummhumm4 小时前
第 36 章 - Go语言 服务网格
java·运维·前端·后端·python·golang·java-ee
White graces6 小时前
Spring MVC练习(前后端分离开发实例)
java·开发语言·前端·后端·spring·java-ee·mvc
frost-cold17 小时前
【JavaEE】Servlet:表白墙
java·servlet·java-ee
界面开发小八哥1 天前
「Java EE开发指南」如何使用Visual JSF编辑器设计JSP?(二)
java·ide·java-ee·开发工具·myeclipse
Nu11PointerException1 天前
JAVA笔记 | 策略模式+枚举Enum简单实现策略模式(可直接套用)
java·spring boot·spring·java-ee·mybatis·个人开发·策略模式
I_Am_Me_1 天前
【JavaEE进阶】SpringBoot 快速上⼿
java·spring boot·java-ee
龙ze1 天前
javaEE初阶——多线程(1)
java-ee
薯条不要番茄酱1 天前
【JavaEE初阶】枫叶经霜艳,梅花透雪香-计算机是如何运行的?
java·开发语言·后端·java-ee·学习方法
鸽鸽程序猿1 天前
【JavaEE】Maven的介绍及配置
java·java-ee·maven