面试题:重量级锁的8连问,你能接住几个?

2023-12-19


前言

接上一篇偏向锁的十连问,继续升级到重量级锁的进阶版,检验一下自己离精通重量级锁还有多远。建议在读之前了解下Java中重量级锁的实现原理。

  1. 重量级锁的ObjectMonitor和JUC中的AQS有什么异同

  2. 为什么ObjectMonitor需要cxq和entryList两个等待队列

  3. cxq队列中等待线程,什么时候会进到EntryList

  4. 等待队列中多个线程,唤醒的顺序是什么

  5. 偏向锁和轻量级锁下线程是否可以wait和notify

  6. cxq和waitset数据结构有什么区别

  7. 被唤醒的wait线程和其它等待线程,谁会先抢到锁

  8. synchronized有类似AQS的公平锁/非公平锁逻辑吗

看了上面的问题,如果是胸有成竹,那就可以跳过这篇文章了。如果一脸问号,这篇文章应该对你有所帮助。


名词解释

首先明确下文章中用到的名词,防止引起误解。

等待队列 ,互斥锁实现中,当线程抢锁失败时,会被放入一个队列等待。当别的线程释放锁后会唤醒队列中的元素重新尝试抢锁,这个队列一般称为互斥等待队列,本文中称为等待队列。

同步队列 ,代码中调用wait方法时,当前线程会放入另外一个队列,等待其它线程notify,这个队列一般称为同步等待队列,本文中称为同步队列。

问题解析

问题1:ObjectMonitor和AQS有什么异同

ObjectMonitor和AQS(AbstractQueuedSynchronizer)都是依据管程模型的原理开发的。所以在整体架构上基本相同,都有共享变量和等待队列,在实现上又有区别。

1) 共享变量 ,ObjectMonitor中使用owner做共享变量,通过CAS设置owner为当前线程来抢锁。而AQS中的共享变量是一个整形的status。因为这一区别,导致ObjectMonitor需要定义一个计数器来记录锁重入次数,而AQS需要额外定义个exclusiveOwnerThread来记录当前持有锁的线程。

2) 等待队列 ,ObjectMonitor等待队列使用了两个队列,cxq和entryList,而AQS仅使用了一个等待队列。

3) 条件同步 ,AQS支持在同一个锁上创建多个条件变量,wait/notify更加灵活和精准。而ObjectMonitor只有一个waitset,所有线程共享一个条件变量。

4) Share模式 ,AQS的Share模式可以使实现读写锁更加简单。

问题2: 为什么ObjectMonitor需要cxq和entryList两个等待队列

ObjectMonitor中加解锁、wait/notify都涉及对等待队列的进出队操作。如果使用一个队列冲突的概率会加大,耗费系统资源。分成2个队列后,出入队EntryList队列只有加锁的情况才会操作,不需要CAS和自旋,减少了资源消耗。

问题3:cxq队列中等待线程,什么时候会进到EntryList

抢锁线程在获取锁失败后,默认会进cxq队列。当持有锁的线程执行完释放锁时,会将cxq中的等待节点放入EntryList中。就是说cxq->EntryList这一步是锁释放之前的由持有锁的线程做的。

问题4:等待队列中多个线程,唤醒的顺序是什么

当持有锁的线程释放锁时,会先检查EntryList是否为空,如果不为空则唤醒EntryList中第一个节点。否则唤醒cxq中第一个节点。EntryList和cxq中出入队策略请看问题6。

问题5:偏向锁和轻量级锁下线程是否可以wait和notify

答案时是可以。原因很简单,因为wait/notify时是需要加入或者唤醒同步队列的,只有ObjectMonitor中才有同步队列。

问题6:cxq和waitset数据结构有什么区别

  1. cxq是一个双向链表,采用先进后出的策略,就是说后入队的线程将先获取到互斥锁,结构如下图:

图片

当前锁被其它线程持有,t0先尝试获取锁,t3最后尝试,cxq当前的状态如上图。最后入队的t3会排在第一位。当持有锁的线程解锁时,正常从队首出队,所以t3首先获得锁。另外,搜索公众号编程技术圈后台回复“大礼包”,获取一份惊喜礼包。

  1. waiset是一个回环链表,即尾节点的下一个节点是头节点,采用先进先出的策略。结构如下图:

图片

问题7:notify/notifyAll后的线程和等待队列中线程,谁会优先抢到锁

使用notify和notifyAll唤醒wait线程,jvm的处理是有区别的。

1)如果是notify,唤醒的是waitset的队首节点,如果这时候EntryList不为空,则放入EntryList,否则放入cxq。无论是放入那个队列。因为是cxq后进先出,所以被唤醒的线程比等待队列中的线程先出队,会先抢到锁。

2)如果是notifyAll,会将waitset中的所有节点逐个放入cxq中。按照问题4中的描述,如果EntryList不为空,则EntryList中首节点会先抢到锁,否则waitset中原最后一个节点先抢到锁,如下图所示:

图片

问题8:Synchronized有类似AQS的公平锁/非公平锁逻辑吗

默认情况下,线程进入重量级锁的抢锁阶段,第一步就会尝试通过自旋来抢锁,所以默认相当于AQS中的非公平锁。即使自旋时未抢到锁,按照上面讲的cxq出入队逻辑,也是后进先出,正常情况下后进入等待队列的线程会先抢到锁,这一点也是和AQS中相反的。

hotspot中对于重量级锁的不同使用场景可以调整这个公平锁逻辑,但是不提供jvm启动参数,需要修改jvm的编译参数来实现。


总结

JVM中的Synchronized重量级锁逻辑和JDK中的AQS都是依据管程模型的理论来设计的,所以有诸多的相似之处。建议感兴趣的读者可以了解下管程模型,对于理解互斥锁会有很大帮助的。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

面试题:重量级锁的8连问,你能接住几个? 的相关文章

  • 如何将本机库链接到 IntelliJ 中的 jar?

    我正在尝试在 IntelliJ 中设置 OpenCV 但是我一直在弄清楚如何告诉 IntelliJ 在哪里可以找到本机库位置 在 Eclipse 中 添加 jar 后 您可以在 Build Config 屏幕中设置 Native 库的位置
  • 如何让 BlazeDS 忽略属性?

    我有一个 java 类 它有一个带有 getter 和 setter 的字段 以及第二对 getter 和 setter 它们以另一种方式访问 该字段 public class NullAbleId private static final
  • Java 枚举与创建位掩码和检查权限的混淆

    我想将此 c 权限模块移植到 java 但是当我无法将数值保存在数据库中然后将其转换为枚举表示形式时 我很困惑如何执行此操作 在 C 中 我创建一个如下所示的枚举 public enum ArticlePermission CanRead
  • 如何循环遍历所有组合,例如48 选择 5 [重复]

    这个问题在这里已经有答案了 可能的重复 如何在java中从大小为n的集合中迭代生成k个元素子集 https stackoverflow com questions 4504974 how to iteratively generate k
  • 过滤两次 Lambda Java

    我有一个清单如下 1 2 3 4 5 6 7 和 预期结果必须是 1 2 3 4 5 6 7 我知道怎么做才能到7点 我的结果 1 2 3 4 5 6 我也想知道如何输入 7 我添加了i gt i objList size 1到我的过滤器
  • 如何获取之前的URL?

    我需要调用我的网络应用程序的 URL 例如 如果有一个从 stackoverflow com 到我的网站 foo com 的链接 我需要 Web 应用程序 托管 bean 中的 stackoverflow 链接 感谢所有帮助 谢谢 并不总是
  • 来自 dll 的 Java 调用函数

    我有这个 python 脚本导入zkemkeeperdll 并连接到考勤设备 ZKTeco 这是我正在使用的脚本 from win32com client import Dispatch zk Dispatch zkemkeeper ZKE
  • 将流转换为 IntStream

    我有一种感觉 我在这里错过了一些东西 我发现自己做了以下事情 private static int getHighestValue Map
  • java.lang.IllegalStateException:提交响应后无法调用 sendRedirect()

    这两天我一直在尝试找出问题所在 我在这里读到我应该在代码中添加一个返回 我做到了 但我仍然得到 java lang IllegalStateException Cannot call sendRedirect after the respo
  • volatile、final 和synchronized 安全发布的区别

    给定一个带有变量 x 的 A 类 变量 x 在类构造函数中设置 A x 77 我们想将 x 发布到其他线程 考虑以下 3 种变量 x 线程安全 发布的情况 1 x is final 2 x is volatile 3 x 设定为同步块 sy
  • tomcat 中受密码保护的应用程序

    我正在使用 JSP Servlet 开发一个Web应用程序 并且我使用了Tomcat 7 0 33 as a web container 所以我的要求是tomcat中的每个应用程序都会password像受保护的manager applica
  • 如何对不同的参数类型使用相同的java方法?

    我的问题 我有 2 个已定义的记录 创建对象请求 更新对象请求 必须通过实用方法进行验证 由于这两个对象具有相同的字段 因此可以对这两种类型应用相同的验证方法 现在我只是使用两种方法进行重载 但它很冗长 public record Crea
  • 如何在谷歌地图android上显示多个标记

    我想在谷歌地图android上显示带有多个标记的位置 问题是当我运行我的应用程序时 它只显示一个位置 标记 这是我的代码 public class koordinatTask extends AsyncTask
  • 为什么 Java 8 不允许非公共默认方法?

    让我们举个例子 public interface Testerface default public String example return Hello public class Tester implements Testerface
  • 专门针对 JSP 的测试驱动开发

    在理解 TDD 到底是什么之前 我就已经开始编写测试驱动的代码了 在没有实现的情况下调用函数和类可以帮助我以更快 更有效的方式理解和构建我的应用程序 所以我非常习惯编写代码 gt 编译它 gt 看到它失败 gt 通过构建其实现来修复它的过程
  • Android:无法使用 DbHelper 和 Contract 类将数据插入 SQLite

    public class Main2Activity extends AppCompatActivity private EditText editText1 editText2 editText3 editText4 private Bu
  • Opencv Java 灰度

    我编写了以下程序 尝试从彩色转换为灰度 Mat newImage Imgcodecs imread q1 jpg Mat image new Mat new Size newImage cols newImage rows CvType C
  • 长轮询会冻结浏览器并阻止其他 ajax 请求

    我正在尝试在我的中实现长轮询Spring MVC Web 应用程序 http static springsource org spring docs 2 0 x reference mvc html但在 4 5 个连续 AJAX 请求后它会
  • 使用 CXF-RS 组件时,为什么我们使用 而不是普通的

    作为后续这个问题 https stackoverflow com questions 20598199 对于如何正确使用CXF RS组件我还是有点困惑 我很困惑为什么我们需要
  • 使用 svn 1.8.x、subclise 1.10 的 m2e-subclipse 连接器在哪里?

    我读到 m2e 的生产商已经停止生产 svn 1 7 以外的任何版本的 m2e 连接器 Tigris 显然已经填补了维护 m2e subclipse 连接器的空缺 Q1 我的问题是 使用 svn 1 8 x 的 eclipse 更新 url

随机推荐