阿里面试题,为什么wait()方法要放在同步块中?

2024-09-02 10:18

本文主要是介绍阿里面试题,为什么wait()方法要放在同步块中?,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

点击上方“朱小厮的博客”,选择“设为星标”

后台回复”加群“加入公众号专属技术群

某天我在***的时候,突然有个小伙伴微信上说:“哥,阿里面试又又挂了,被问到为什么wait()方法要放在同步块中,没答出来!”

我顿时觉得**一紧,仔细回顾一下,如果wait()方法不在同步块中,代码的确会抛出异常:

  1. public class WaitInSyncBlockTest {

  2. @Test

  3. public void test() {

  4. try {

  5. new Object().wait();

  6. } catch (InterruptedException e) {

  7. e.printStackTrace();

  8. }

  9. }

  10. }

结果是:

但是,为毛呢??我也没去了解过。

机智如我立刻假装正在开会忙得不可开交,回了一条:“开会中,等会和你细说。”

经过一番谷歌之后,找到了答案。

Lost Wake-Up Problem

事情得从一个多线程编程里面臭名昭著的问题"Lost wake-up problem"说起。

这个问题并不是说只在Java语言中会出现,而是会在所有的多线程环境下出现。

假如有两个线程,一个消费者线程,一个生产者线程。生产者线程的任务可以简化成将count加一,而后唤醒消费者;消费者则是将count减一,而后在减到0的时候陷入睡眠:

生产者伪代码:

  1. count+1;

  2. notify();

消费者伪代码:

  1. while(count<=0)

  2. wait()

  3. count--

熟悉多线程的朋友一眼就能够看出来,这里面有问题。什么问题呢?

生产者是两个步骤:

  1. count+1;

  2. notify();

消费者也是两个步骤:

  1. 检查count值;

  2. 睡眠或者减一;

万一这些步骤混杂在一起呢?比如说,初始的时候count等于0,这个时候消费者检查count的值,发现count小于等于0的条件成立;就在这个时候,发生了上下文切换,生产者进来了,噼噼啪啪一顿操作,把两个步骤都执行完了,也就是发出了通知,准备唤醒一个线程。这个时候消费者刚决定睡觉,还没睡呢,所以这个通知就会被丢掉。紧接着,消费者就睡过去了……

这就是所谓的lost wake up问题。

那么怎么解决这个问题呢?

现在我们应该就能够看到,问题的根源在于,消费者在检查count到调用wait()之间,count就可能被改掉了。

这就是一种很常见的竞态条件。

很自然的想法是,让消费者和生产者竞争一把锁,竞争到了的,才能够修改count的值。

于是生产者的代码是:

  1. tryLock()

  2. count+1

  3. notify()

  4. releaseLock()

消费者的代码是:

  1. tryLock()

  2. while(count <= 0)

  3. wait()

  4. count-1

  5. releaseLock

注意的是,我这里将两者的两个操作都放进去了同步块中。

现在来思考一个问题,生产者代码这样修改行不行?

  1. tryLock()

  2. count+1

  3. notify()

  4. releaseLock()

答案是,这样改毫无卵用,依旧会出现lost wake up问题,而且和无锁的表现是一样的。

终极答案

所以,我们可以总结到,为了避免出现这种lost wake up问题,在这种模型之下,总应该将我们的代码放进去的同步块中。

Java强制我们的wait()/notify()调用必须要在一个同步块中,就是不想让我们在不经意间出现这种lost wake up问题。

不仅仅是这两个方法,包括java.util.concurrent.locks.Condition的await()/signal()也必须要在同步块中:

  1. private ReentrantLock lock = new ReentrantLock();

  2. private Condition condition = lock.newCondition();

  3. @Test

  4. public void test() {

  5. try {

  6. condition.signal();

  7. } catch (Exception e) {

  8. e.printStackTrace();

  9. }

  10. }

准确的来说,即便是我们自己在实现自己的锁机制的时候,也应该要确保类似于wait()和notify()这种调用,要在同步块内,防止使用者出现lost wake up问题。

Java的这种检测是很严格的。它要求的是,一定要处于锁对象的同步块中。举例来说:

  1. private Object obj = new Object();

  2. private Object anotherObj = new Object();

  3. @Test

  4. public void produce() {

  5. synchronized (obj) {

  6. try {

  7. anotherObj.notify();

  8. } catch (Exception e) {

  9. e.printStackTrace();

  10. }

  11. }

  12. }

这样是没有什么卵用的。一样出现IllegalMonitorStateException。

可以拿去套路面试官的话术

到这里,按照道理来说,就可以结束了。不过既然是面试遇到的问题,我就提供点面试回答的小技巧。

假如面试官问你这个问题了,你最开始不要巴啦啦全部说出来。只需要轻描淡写地说:“这是Java设计者为了避免使用者出现lost wake up问题而搞出来的。”

注意演技,一定要轻描淡写中透露着一丝“我其实就知道lost wake up这个名词,再问就要露馅了”的感觉。

于是面试官肯定会追问:“lost wake up问题是什么?”

这个时候你就可以巴啦啦一大堆了。这个过程你要充满自信,表露出那种睥睨天下这种小问题就别来烦我的气概来。

于是,小手一抖,offer到手。

想知道更多?描下面的二维码关注我

怎么加群?:>>>Learn More<<

怎么免费加入知识星球:>>>Free<<<

免费资料入口:后台回复“666”

朕已阅 

这篇关于阿里面试题,为什么wait()方法要放在同步块中?的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1129687

相关文章

Java中流式并行操作parallelStream的原理和使用方法

《Java中流式并行操作parallelStream的原理和使用方法》本文详细介绍了Java中的并行流(parallelStream)的原理、正确使用方法以及在实际业务中的应用案例,并指出在使用并行流... 目录Java中流式并行操作parallelStream0. 问题的产生1. 什么是parallelS

MySQL数据库双机热备的配置方法详解

《MySQL数据库双机热备的配置方法详解》在企业级应用中,数据库的高可用性和数据的安全性是至关重要的,MySQL作为最流行的开源关系型数据库管理系统之一,提供了多种方式来实现高可用性,其中双机热备(M... 目录1. 环境准备1.1 安装mysql1.2 配置MySQL1.2.1 主服务器配置1.2.2 从

Python版本信息获取方法详解与实战

《Python版本信息获取方法详解与实战》在Python开发中,获取Python版本号是调试、兼容性检查和版本控制的重要基础操作,本文详细介绍了如何使用sys和platform模块获取Python的主... 目录1. python版本号获取基础2. 使用sys模块获取版本信息2.1 sys模块概述2.1.1

Python实现字典转字符串的五种方法

《Python实现字典转字符串的五种方法》本文介绍了在Python中如何将字典数据结构转换为字符串格式的多种方法,首先可以通过内置的str()函数进行简单转换;其次利用ison.dumps()函数能够... 目录1、使用json模块的dumps方法:2、使用str方法:3、使用循环和字符串拼接:4、使用字符

Python版本与package版本兼容性检查方法总结

《Python版本与package版本兼容性检查方法总结》:本文主要介绍Python版本与package版本兼容性检查方法的相关资料,文中提供四种检查方法,分别是pip查询、conda管理、PyP... 目录引言为什么会出现兼容性问题方法一:用 pip 官方命令查询可用版本方法二:conda 管理包环境方法

Linux云服务器手动配置DNS的方法步骤

《Linux云服务器手动配置DNS的方法步骤》在Linux云服务器上手动配置DNS(域名系统)是确保服务器能够正常解析域名的重要步骤,以下是详细的配置方法,包括系统文件的修改和常见问题的解决方案,需要... 目录1. 为什么需要手动配置 DNS?2. 手动配置 DNS 的方法方法 1:修改 /etc/res

JavaScript对象转数组的三种方法实现

《JavaScript对象转数组的三种方法实现》本文介绍了在JavaScript中将对象转换为数组的三种实用方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友... 目录方法1:使用Object.keys()和Array.map()方法2:使用Object.entr

SpringBoot中ResponseEntity的使用方法举例详解

《SpringBoot中ResponseEntity的使用方法举例详解》ResponseEntity是Spring的一个用于表示HTTP响应的全功能对象,它可以包含响应的状态码、头信息及响应体内容,下... 目录一、ResponseEntity概述基本特点:二、ResponseEntity的基本用法1. 创

java中判断json key是否存在的几种方法

《java中判断jsonkey是否存在的几种方法》在使用Java处理JSON数据时,如何判断某一个key是否存在?本文就来介绍三种方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的... 目http://www.chinasem.cn录第一种方法是使用 jsONObject 的 has 方法

java中ssh2执行多条命令的四种方法

《java中ssh2执行多条命令的四种方法》本文主要介绍了java中ssh2执行多条命令的四种方法,包括分号分隔、管道分隔、EOF块、脚本调用,可确保环境配置生效,提升操作效率,具有一定的参考价值,感... 目录1 使用分号隔开2 使用管道符号隔开3 使用写EOF的方式4 使用脚本的方式大家平时有没有遇到自