面试官:为什么有了sleep还需要wait?

这篇具有很好参考价值的文章主要介绍了面试官:为什么有了sleep还需要wait?。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

面试官:为什么有了sleep还需要wait?

1. 能不能调整线程先后顺序?

对于线程执行最大的问题就是随机调度,抢占式执行,对于程序猿来讲,是不喜欢这种随机性的,程序猿喜欢确定的东西,于是就有了一些方法,可以控制线程之间的执行顺序,虽然线程在内核里调度是随机的,但我们可以通过一些 api 让线程主动阻塞等待,主动放弃 CPU 给其他线程让路呀!

就比如说,在地铁上,张三看到一位老人上地铁了,主动让座,老人坐了一会,起身对小伙说,我还有一站就到了,你来坐着吧,我站一会就下车了。

这是不是就像线程1正在占用 CPU 资源了,突然线程2开始工作了,于是线程1就让线程2先去工作,等线程2工作差不多了,在通知线程1可以工作了。

在实际开发中,很多时候线程之间是需要相互配合的。

比如篮球哥喜欢打篮球,篮球里,一个队伍五个人,小前锋,大前锋,中锋,后卫,分位,这 5 个角色就像 5 个线程,如果这 5 个人都争这一个球,那这个的队伍就没有配合性,必定会输球。

如果这 5 个人打好配合,先谁持球,然后接着执行什么战术,有合理的战术安排,此时球就能很好的在这 5 个人的手里运作起来,进球的概率也就大大提升。

再比如,球员a 先持球过半场,传球给球员b,球员b接球就投,球进了!

此时是不是就需要 a 先拿球过半场啊,等 a 过了半场,在传球给 b ,在 a 没有传球之前 b 是不能拿到球的!

也就是线程1没有执行到一定阶段,线程2是不能工作的!

对于完成上述的配合操作,主要涉及到三个方法:

  • wait() / wait(long timeout)

  • notify / notiryAll()

此处的方法都是 Object 类中的方法,Object 类是所有类的父类,所以所有对象都有上述方法。

后续的内容也是围绕上述方法进行展开。


2. wait 方法

当某个对象调用 wait 方法时,wait 会做如下三件事:

  • wait 使当前执行代码的线程进行等待(把线程放到阻塞队列中)

  • 释放当前的锁

  • 满足一定条件时被唤醒,重新尝试获取这个锁

由于 wait 执行时会释放当前的锁,所以调用 wait 的时候需要先获取到锁,即 wait 需要搭配 synchronized 使用。

wait 的结束条件(满足一个即可):

  • 其他线程调用该对象的 notify 方法

  • wait 超时等待(wait 方法提供一个带有 timeout 参数的版本, 来指定等待时间)

  • 其他线程调用该线程的 interrupted 方法,导致 wait 抛出 InterruptedException 异常


public static void main(String[] args) {
    Object object = new Object();
    Thread t1 = new Thread(() -> {
        synchronized (object) {
            System.out.println("开始等待!!!");
            try {
                object.wait();
                System.out.println("等待结束!!!");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    });
    t1.start();
}

显然上述这个代码是一个 "死等",因为没有触发上述 wait 结束条件的任意一个,所以 t1 线程会无止境的等待下去:

面试官:为什么有了sleep还需要wait?

通过 jconsole 工具也能发现,t1 线程始终处于 WAITING 状态!

如何让 wait 结束,那么只要满足上述所说的三个 wait 结束条件即可。


3. notify 方法

notify 的作用是唤醒等待的线程

  • notify 这个方法也要在同步代码块或同步方法中执行(被synchronized 修饰),notify 用于通知哪些可能等待该对象锁的其他线程,并使他们重新获取该对象的锁。

  • 如果有多个线程等待该对象的锁, 则由线程调度器随机挑选出一个呈 wait 状态的线程,并不会采取先来后到的机制。

  • notify 方法后,当前线程不会马上释放该对象锁,要等到执行完 notify 所处被 synchronized 修饰的代码块执行结束,才能释放对象锁!

注意,通过指定对象调用 wait() 进入 WAITING 状态的线程,只有指定对象调用 notify 唤醒后(特殊情况除外),该线程才能尝试获取锁,接着往下执行!

notify 就好像一个妈妈(指定对象),妈妈手上拿着一块小蛋糕,有三个小朋友在桌子旁边坐着等(妈妈.wait),妈妈随机喊了一个小朋友,让他来吃蛋糕(妈妈.notify),但是妈妈并没有把蛋糕放下(没有结束对应代码块,也就是还未释放锁),当妈妈把蛋糕放在桌子上(锁被释放),这个小朋友才能去吃蛋糕(获取到锁)。

面试官:为什么有了sleep还需要wait?

此时有了上述知识,我们就可以实现下上述图中吃蛋糕的场景了(为了代码简洁,我们只设定两个线程来等待被唤醒):


public static void main(String[] args) throws InterruptedException {
    Object object = new Object();
    Thread t1 = new Thread(() -> {
        synchronized (object) {
            System.out.println("张三进入 WAITING 状态");
            try {
                object.wait();
                System.out.println("张三吃到蛋糕了!");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    });

    Thread t2 = new Thread(() -> {
        synchronized (object) {
            System.out.println("李四进入 WAITING 状态");
            try {
                object.wait();
                System.out.println("李四吃到蛋糕了!");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    });

    t1.start();
    t2.start();
    Thread.sleep(10); // 保证两个线程都进入到 WAITING 状态
    synchronized (object) {
        object.notify();
    }
}
面试官:为什么有了sleep还需要wait?

可能大家多次测试上述代码后,发现一直都是张三吃到了蛋糕啊,但是其实这个是随机的,因为 CPU 就是随机调度的,这个咱们就没必要钻牛角尖了,实在要钻,可以创建线程池(后续讲),搞一堆线程进行测试即可。

此时问题来了,当释放锁了之后,也就是妈妈把蛋糕放在桌子上了,此时被唤醒的线程是可以去拿蛋糕的,但是有没有可能释放锁的瞬间,被其他处在 RUNNABLE 状态的线程给劫持了呢?(其他线程也来竞争这把锁) 也就是突然冲进来了一条小狗,把蛋糕给抢到了,其实是有这种情况的:


public static void main(String[] args) throws InterruptedException {
    Object object = new Object();
    Thread t1 = new Thread(() -> {
        synchronized (object) {
            System.out.println("张三进入 WAITING 状态");
            try {
                object.wait();
                System.out.println("张三吃到蛋糕了!");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    });

    Thread t2 = new Thread(() -> {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        synchronized (object) {
            System.out.println("小狗把蛋糕抢走了!");
            while (true) {} // 吃蛋糕
        }
    });

    t1.start();
    t2.start();
    Thread.sleep(1000); // 保证两个线程都进入到 WAITING 状态
    synchronized (object) {
        object.notify();
        Thread.sleep(1000); // 唤醒 t1 但并没有立即释放锁, 休眠 1s 再释放
    }
}

上述代码 main 线程等待 1000 毫秒后唤醒 t1 线程,此时 t1 被唤醒,就会重新尝试获取 object 对象锁,但是 t2 休眠了 1000 毫秒后,也想获取 object 对象锁。

唤醒 t1 之后,过了 1000 毫秒,也就意味着锁被释放,此时 t1 和 t2 都想获取到 object 对象锁,那究竟谁能获取到呢?这完全是随机的!比如下面的测试结果:

面试官:为什么有了sleep还需要wait?

所以是有可能别半路截胡的,罪魁祸首还是因为随机调度,抢占式执行呀!所以以后在写多线程代码的时候一定要多多注意,要让每种执行顺序得到的结果都是一样的,这才是好的代码!

关于 notifyAll :

notifyAll 和 notify 非常相似,假设 5 个线程等待 object 对象唤醒,然后 object.notifyAll(),就会将这 5 个线程全部唤醒,然后这 5 个线程竞争 object 对象锁,没竞争到的,就继续进入 WAITING 状态。


4. 使用 wait 和 notify 注意点

一定要弄清楚是谁在等被谁唤醒!

如果 t1 里面调用 o1.wait(),那么只有其他线程调用了 o1.notify() 才能唤醒 t1,如果是其他线程调用 o2.notify(),是不能唤醒 t1 的!因为 t1 线程是在等 o1 唤醒!

而 o1 也只能唤醒在等他的线程,比如 t3 在等 student 唤醒,那调用 o1.notify() 是不能唤醒 t3 的,只能调用 studnet.notify() 才能唤醒 t3。

归根到底,我们一定要弄清楚,线程在等谁,也要弄清楚,这个对象,有哪些线程在等他的唤醒!

这里举两个例子来演示一下:


public static void main(String[] args) throws InterruptedException {
    Object o1 = new Object();
    Object o2 = new Object();
    Thread t = new Thread(() -> {
        synchronized (o1) {
            try {
                o1.wait();
                System.out.println("t 线程被 main 线程唤醒!");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    });
    t.start();
    Thread.sleep(10); // 保证 t 线程进入 WAITING 状态
    synchronized (o2) {
        o2.notify();
        System.out.println("执行完 o2.notify!");
    }
}

这段代码,t 线程在等待 o1 对象唤醒,所以 main 线程中 o2.notify() 是在唤醒等待 o2 的线程,显然没有线程在等待 o2 唤醒,所以空打一枪,然而 t 线程仍然处在 WAITING 状态。

面试官:为什么有了sleep还需要wait?

如果对应对象 notify 的时候,没有线程在等待这个对象唤醒呢?那么就是无效唤醒,也没有什么副作用,所以我们以后写代码的时候还是要尽量保证先执行 wait 在执行 notify 才是有意义的,也就是在 notify 的时候,有线程在等待这个对象唤醒!


5. wait 带参数和 sleep 的区别

wait 的带参数版本,指定了最大的等待时间,看起来和 sleep 有点像,但是还是有本质区别的。

  • notify 唤醒 wait 的时候,是不会有任何异常的(正常的业务逻辑)

  • interrupt 唤醒 sleep 的时候,则是会抛出一个异常(表示逻辑出现了问题)

其实从理论上,wait 和 sleep 是没得比的,wait 是线程之间的通信,互相配合,而 sleep 是单纯让线程阻塞一段时间,唯一的相同点就是都可以让线程放弃 CPU 的调度一段时间而已。

  • wait 是需要搭配 synchronized 使用的,sleep 则不需要

  • wait 是 Object 的方法,而 sleep 是 Thread 的静态方法


下期预告:【多线程】单例模式文章来源地址https://www.toymoban.com/news/detail-449096.html

到了这里,关于面试官:为什么有了sleep还需要wait?的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Java 中有了基本类型为什么还需要包装类?

    Java 中有8种基本数据类型,这些基本类型又都有对应的包装类。 分类 基本数据类型 包装类 布尔型 boolean Boolean 整型 byte Byte short Short int Integer long Long 字符型 char Character 浮点型 float Float double Double 因为 Java 是一种面向对象语言,很多地方都需要使用对象而不是基本数据类型。

    2024年02月14日
    浏览(43)
  • 为什么需要 TIME_WAIT 状态

    还是用一下上一篇文章画的图 TCP 的 11 个状态,每一个状态都缺一不可,自然 TIME_WAIT 状态被赋予的意义也是相当重要,咱们直接结论先行 上文我们提到 tcp 中,主动关闭的一边会进入 TIME_WAIT 状态, 另外 Tcp 中的有 TIME_WAIT 状态,主要是有如下 2 个原因: 为了防止被动关闭一

    2024年02月09日
    浏览(45)
  • 面试官灵魂拷问:什么是MySQL索引?为什么需要索引?

    关系型数据库是一种采用关系模型存储数据的数据库系统。在关系型数据库中,数据被组织成一个或多个表格(也称为关系),每个表格包含多行记录,每行记录代表一组相关数据。每个表格都有一个定义该表格中数据的结构的模式,即表格的列和每个列的数据类型。关系型

    2024年02月09日
    浏览(62)
  • 为什么跟踪崩溃时,经常死在sleep?

    上周跟踪崩溃,结论非常奇怪,似乎死在sleep。sleep是正宗系统函数,这个也动不动崩溃,那系统每分钟都要死几次。所以肯定与sleep无关。 那么为什么看起来死在sleep上?其实这是一种错觉: 执行工作时的速度很快,比如不到1毫秒。 剩下的时间都在sleep。 崩溃的时候,大概

    2024年01月22日
    浏览(48)
  • 有了MySQL,为什么还要有NoSQL

        🏆今日学习目标: 🍀MySQL和NoSQL的区别 ✅ 创作者 :林在闪闪发光 ⏰预计时间:30分钟 🎉个人主页:林在闪闪发光的个人主页  🍁林在闪闪发光的个人社区,欢迎你的加入: 林在闪闪发光的社区 目录 noSQL的大概意思 理论支撑 为什么需要NoSQL 为什么NoSQL有处理超大规模

    2023年04月20日
    浏览(76)
  • Java中有了equals(),为什么还有“==“

    背景:Java中“一切皆是对象”,为什么还有非对象的“==”? 在Java语言假设我们只进行OOP,所以Java代码都是由一个接着一个的类组成的。那么,对象之间比较,用equals()就可以了。 可为什么“==”在代码随处可见呢? Java是基于C++的,相比与C++,Java是一种更“纯粹”的面向

    2023年04月20日
    浏览(57)
  • 为什么有了 HTTP 还要 RPC

    哈喽大家好,我是咸鱼 随着互联网技术的发展,分布式架构越来越被人们所采用。在分布式架构下, 为了实现复杂的业务逻辑,应用程序需要分布式通信实现远程调用 而这时候就需要一种协议来支持远程过程调用,以便实现不同应用程序之间的数据交换和信息传递。其中常

    2024年02月05日
    浏览(48)
  • 为什么要学习Python呢?有了 ChatGPT 还有必要学习 python 吗?

    学习 Python 的原因有很多,以下是一些常见的原因: 简单易学: Python 是一门易于学习的编程语言,语法简单、清晰明了,可以快速掌握基本的编程概念。 应用广泛: Python 是一门通用的编程语言,可用于 Web 开发、数据分析、机器学习、人工智能、游戏开发等多个领域。 巨

    2024年02月03日
    浏览(67)
  • mysql面试题30:什么是数据库连接池、应用程序和数据库建立连接的过程、为什么需要数据库连接池、你知道哪些数据库连接池

    该文章专注于面试,面试只要回答关键点即可,不需要对框架有非常深入的回答,如果你想应付面试,是足够了,抓住关键点 数据库连接池是一种用于管理和复用数据库连接的技术。它是在应用程序和数据库之间建立一组数据库连接,并以池的形式存储起来,每当应用程序需

    2024年02月07日
    浏览(58)
  • 97-TCP为什么要有一个“TIME_WAIT“的状态

    \\\"TIME_WAIT\\\"状态存在的原因主要有两点: 假设上图中用于确认服务器结束报文段6的TCP报文段7丢失,那么服务器将重发结束报文段,因此客户端需要停留在某个状态以处理重复收到的结束报文段.否则客户端将以复位报文段来回应服务器,服务器则认为这是一个错误,因为他期望的是一

    2024年02月01日
    浏览(62)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包