[JavaEE系列] wait(等待) 和 notify(唤醒)

目标

        本文最终目的是要熟练掌握 wait 和 notify 方法的各种使用场景, 最后再对 wait 和 sleep 进行比较区分.

为什么需要 wait 和 notify ?

        在此之前, 我们控制线程之间的执行顺序的时候, 一是使用 sleep 让某一个线程进入休眠状态, 其他线程可以在此期间执行; 二是使用 synchronized 对线程中的某个部分加锁, 这时候其他线程再访问这个锁的时候就会发生阻塞等待.
        但是使用这两种方法来对线程的执行顺序进行控制是比较麻烦的, 有时候也会出现不准确的情况. 对此, 我们有了一种更为合理的方法来让线程达到能够按照我们指定安排的顺序执行的效果. 那就是使用 wait 方法来让线程进行等待, 以及使用 notify 方法来对线程进行唤醒.

详解 wait 和 notify

wait (等待)

        使用 wait 方法的前提是需要获取到锁(也就是说, wait 必然存在于 synchronized 中, 并且调用 wait 的对象和 synchronized 里使用的锁对象是一个对象的), 因为 wait 方法的功能是: 1. 释放原有的锁; 2. 等待通知(唤醒); 3. 当通知到达后, 尝试重新获取到锁(这就涉及到下面 notify 的东西了).

notify (唤醒)

        使用 notify 方法则是起到了通知 / 唤醒的效果. 当然, 这里调用 notify 方法的对象需要和调用 wait 方法的对象是一样的(也就是和前面 synchronized 里使用的锁对象是一样的). 其次, notify 还有一个重要的特点: 就是在使用 notify 的时候, 不一定需要有 wait, 换句话说, 无论在什么时候使用 notify, 都是无副作用的.

一段代码了解 wait 和 notify 之间的运行过程

public class Main {
    public static Object locker = new Object();

    public static void main(String[] args) {
        //创建一个用来等待的线程
        Thread waitTask = new Thread(() -> {
            synchronized (locker){
                try {
                    System.out.println("wait之前");
                    locker.wait();
                    System.out.println("wait之后");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        waitTask.start();

        //创建一个用来唤醒的线程
        Thread notifyTask = new Thread(() -> {
            //让用户控制, 输入内容之后再执行唤醒
            Scanner scanner = new Scanner(System.in);
            System.out.println("输入任意内容执行唤醒:");
            scanner.next();  //此处next会发生阻塞,直到输入内容
            synchronized (locker){
                System.out.println("notify开始");
                locker.notify();
                System.out.println("notify结束");
            }
        });
        notifyTask.start();
    }
}

运行结果:
在这里插入图片描述

        过程: 当 waitTask 线程执行到 wait 的时候, 会先释放 locker 锁, 然后进入阻塞等待, 当再执行接下来的 notifyTask 线程的时候, 执行到 notify 的时候, 就会唤醒之前的 wait, 将PCB由阻塞队列加入到就绪队列中来, 接着继续往下执行这个线程(这里面涉及到的锁对象都是 locker).

        注意: wait 和 notify 机制还能非常有效地避免"线程饿死"的现象, 那么什么是"线程饿死"呢? "线程饿死"就是在某些情况下(具体就是操作系统对线程进行随机调度的原因), 有些线程反复占用CPU, 而有些线程则是始终都进不了CPU执行(正所谓旱的旱死, 涝的涝死), 简单来说就是调度器可能会出现线程分配不均匀的情况.

总结

wait 和 sleep 的区别(重要)

  • 共同点: 它们都是使线程暂停一段时间的方法, 其中, 它们都可以进行死等, 也可以指定最长等待时间.

  • 不同点:

    • wait是Object类中的一个方法, sleep是Thread类中的一个方法
    • wait必须在synchronized修饰的代码块或方法中使用, sleep方法可以在任何位置使用
    • wait被调用后当前线程进入BLOCK状态并释放锁, 并可以通过notify和notifyAll方法进行唤醒; 而sleep被调用后当前线程进入TIMED_WAIT状态, 不涉及锁相关的操作
  • 1
    点赞
  • 0
    收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

蔡欣致

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

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

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

打赏作者

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

抵扣说明:

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

余额充值