Java Web 实战 06 - 多线程基础之 wait 和 notify 关键字

大家好~ , 这篇文章想给大家带来两个多线程中的关键字 : wait 和 notify , 他们俩的作用都是用来控制线程之间的执行顺序的 , 但是有什么区别 , 我们继续往下学习
推荐大家跳转到此页面进行查看~
上一篇文章的链接在这里~
在这里插入图片描述

七 . wait 和 notify

他们俩的作用是用来控制线程之间的执行顺序的
我们之前通过 join 实现了线程的结束顺序 , 但是远远不够
wait 和 notify 能够更好的控制线程之间的执行顺序


wait 叫做 等待 , 调用 wait 的线程就会进入阻塞等待的状态 (进入 WAITING 状态)
notify 叫做 唤醒 , 调用 notify , 就可以把对应的 wait 线程唤醒 , 让他从阻塞队列恢复到就绪状态

wait 和 notify 都是 Object 的成员方法 , 谁等待就由谁唤醒

o1.wait();
// 使用 o1.notify() 就可以唤醒 调用 o1.wait() 的线程
// 使用 o1.notify() 就不可以唤醒 调用 o1.wait() 的线程

对于 wait 来说 , 内部的执行过程 , 还有点复杂

  1. 释放锁
  2. 等待通知
  3. 当通知到达之后 , 就会被唤醒 , 尝试重新获取锁

image.png
对于 notify 来说 , 内部的执行过程非常简单 , 只有进行通知这一件事

我们先来看一眼 wait 是怎么回事

7.1 wait 方法

public class Demo17 {
    public static void main(String[] args) throws InterruptedException {
        // wait 方法是 Object 的成员方法,需要先创建 Object 实例
        Object object = new Object();

        System.out.println("wait 之前:");
        object.wait();
        System.out.println("wait 之后:");
    }
}

image.png
我们需要在 synchronized内部使用

public class Demo17 {
    public static void main(String[] args) throws InterruptedException {
        // wait 方法是 Object 的成员方法,需要先创建 Object 实例
        Object object = new Object();

        synchronized (object) {
            System.out.println("wait 之前:");
            object.wait();
            System.out.println("wait 之后:");
        }
    }
}

image.png
我们也可以使用 jconsole 工具来看一下当前线程的状态
image.png
image.png

7.2 notify 方法

notify 方法也需要搭配 synchronized使用

import java.util.Scanner;

// 创建两个线程,一个线程调用 wait,一个线程调用 notify
public class Demo18 {
    // 这个对象用来做锁对象
    public static Object Locker = new Object();
    public static void main(String[] args) {
        // t1 线程用来等待
        Thread t1 = new Thread(() -> {
            synchronized (Locker) {
                System.out.println("wait 开始");
                try {
                    Locker.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("wait 结束");
            }

        });
        t1.start();

        // t2 线程用来唤醒
        Thread t2 = new Thread(() -> {
            // 这里让用户输入内容之后,再执行通知
            Scanner scanner = new Scanner(System.in);
            System.out.println("输入任意内容,我们开启通知");
            // 用户输入什么不重要
            // next 会发生阻塞,直到用户真正输入内容以后
            scanner.next();

            synchronized (Locker) {
                System.out.println("notify 开始");
                Locker.notify();
                System.out.println("notify 结束");
            }
        });
        t2.start();
    }
}

image.png
所以我们就可以控制线程的顺序了

线程 1 需要先计算出一个结果
线程 2 需要使用这个结果
我们就可以让线程 2 进行 wait , 线程 1 计算完结果之后 , notify 唤醒线程 2

本来线程1 线程2 是并发执行的 , 通过 wait notify 搭配使用 , 就可以实现正确的计算了

notify 这个东西 , 调用的时候 , 会尝试进行通知
如果当前对象没有在其他线程中 wait , 则不会有副作用

如果 wait 是一个对象 , notify 是另一个对象 , 则无法唤醒

import java.util.Scanner;

// 创建两个线程,一个线程调用 wait,一个线程调用 notify
public class Demo18 {
    // 这个对象用来做锁对象
    public static Object Locker1 = new Object();
    public static Object Locker2 = new Object();
    public static void main(String[] args) {
        // t1 线程用来等待
        Thread t1 = new Thread(() -> {
            synchronized (Locker1) {
                System.out.println("wait 开始");
                try {
                    Locker1.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("wait 结束");
            }

        });
        t1.start();

        // t2 线程用来唤醒
        Thread t2 = new Thread(() -> {
            // 这里让用户输入内容之后,再执行通知
            Scanner scanner = new Scanner(System.in);
            System.out.println("输入任意内容,我们开启通知");
            // 用户输入什么不重要
            // next 会发生阻塞,直到用户真正输入内容以后
            scanner.next();

            synchronized (Locker2) {
                System.out.println("notify 开始");
                Locker2.notify();
                System.out.println("notify 结束");
            }
        });
        t2.start();
    }
}

image.png

7.3 wait 和 notify 还可以有效避免 “线程饿死”

线程饿死指的是 : 有些情况下 , 调度器可能分配任务不均匀 , 导致有些线程反复占用 CPU , 有些线程始终捞不着 CPU (涝的涝死 , 旱的旱死)
image.png

线程进入了 WAITING 状态 , 则务必需要其他的线程来主动唤醒 (notify)
线程进入了 BLOCKED 状态 , 则是其他线程把锁释放之后 , 操作系统负责唤醒
线程进入了 TIMED_WAITING 状态 , 操作系统会计时 , 时间到了之后来进行唤醒

这三个阻塞的状态都会导致 PCB 进入内核中对应的阻塞队列

7.4 notifyAll 方法

当有多个线程等待的时候, , notify 是一次唤醒一个 (从若干个里面随机挑一个)
notifyAll 则是唤醒所有 (再由这些线程去竞争锁)

notify 只唤醒等待队列中的一个线程 , 其他线程还是乖乖等着
notifyAll 一下全都唤醒 , 需要这些线程重新竞争锁
image.png

7.5 wait 和 sleep 的区别 (面试题)

他们俩都会让线程进入阻塞
但是阻塞的原因和目的不同 , 进入的状态也不同 , 被唤醒的条件也不同

进入的状态 : wait 对应的是 WAITING 状态 , sleep 对应的是 TIMED-WAITING 状态
被唤醒的条件 : wait 对应的是主动被唤醒 , sleep 对应的是时间到了就被自动唤醒
阻塞的原因和目的 : wait 是控制线程之间执行的先后顺序 , 而 sleep 实际开发很少用到

sleep 的目的是 “放权” , 暂时让出当前 CPU 的使用权
之所以 sleep 用的比较少 , 是因为等的时间太过于固定了 , 要是有突发情况想提前唤醒 , 并不是那么容易

  • 9
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 12
    评论
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

加勒比海涛

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

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

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

打赏作者

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

抵扣说明:

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

余额充值