Synchronized和ReentrantLock面试详解

news2025/2/8 19:07:28

前言

接下来为大家带来的是 Java 中的两个典型锁代表:Synchronized 和 ReentrantLock 的详解

面试题:谈一谈AQS

在说 ReentrantLock 时,有必要先了解一下 AQS,因为 ReentrantLock 就是基于 AQS 实现的

分析:

共享状态 volatile 修饰的 state + FIFO 队列,来管理线程对共享资源的访问

回答:

切记不要长篇大论,容易绕进去

简单来说 AQS 就是起到了一个抽象,封装的作用,将一些排队,入队,加锁,等方法提供出来,便于其他相关 JUC 锁的使用,具体加锁时机,入队时机等都需要实现类自己控制

它主要通过维护一个共享状态 state 和一个先进先出的 FIFO 等待队列,来管理线程对共享资源的访问

state 用 volatile 修饰,表示当前资源的状态。例如,在独占锁中,0 表示未被占用,1 表示已被占用

当线程尝试获取资源失败时,会被加入到 AQS 的等待队列中。队列采用双向链表结构,节点包含线程的引用,等待状态以及前驱和后继节点的指针

AQS 的常见实现类有:ReentrantLock,Semaphore 等等

然后可能会被追问 ReentrankLock 实现原理

面试题:ReentrankLock 实现原理

分析:

基于 AQS 的可重入锁,支持公平和非公平两种 依靠 state 变量和两种队列:同步队列和等待队列

回答:

ReentrantLock 其实就是基于 AQS 实现的一个可重入锁,支持公平和非公平两种方式。

内部实现依靠一个 state 变量和两种队列:同步队列和等待队列,等待队列可以有多个,具体看 condition 条件的数量

利用 CAS 修改 state 来争抢锁

争抢不到则入同步队列等待,同步队列是一个双向链表

条件 condition 不满足时则入等待队列(当条件满足时,从等待队列中出来的线程会尝试直接获取锁,而不是先进入同步队列,失败再进入同步队列),是个单向链表

是否是非公平锁的区别在于:线程获取锁时是加入到同步队列尾部还是直接利用 CAS 争抢锁

一、等待队列中线程的唤醒顺序

  • 默认情况:在 ReentrantLockCondition 实现中,如果没有特别指定,当条件满足时,唤醒等待队列中的线程顺序是不确定的。它通常是基于底层实现的一些规则,可能与线程进入等待队列的顺序没有直接的固定关联。例如,可能是根据底层数据结构(如链表)的遍历顺序或者其他内部机制来决定先唤醒哪个线程。

  • signal()signalAll() 的影响:

    • 当调用 condition.signal() 方法时,通常只会唤醒等待队列中的一个线程,但具体是哪个线程被唤醒是不确定的,可能是任意一个等待线程。

    • 而调用 condition.signalAll() 方法时,会唤醒等待队列中的所有线程,这些线程会竞争获取锁,获取到锁的线程可以继续执行,其他线程则会进入同步队列等待获取锁的机会。

二、Condition 与等待队列的关系

  • 一个 Condition 一个等待队列:每个 Condition 对象都有自己独立的等待队列。这意味着不同的 Condition 可以用于不同的等待条件和线程协作场景,并且它们各自管理自己的等待线程队列。

    • 例如,在一个复杂的多线程应用中,可能有多个不同的条件需要线程等待,如数据准备好、资源可用、某个标志位设置等。可以为每个这样的条件创建一个 Condition 对象,每个 Condition 的等待队列中存放着等待相应条件的线程。当某个条件满足时,通过对应的 Condition 对象的 signal()signalAll() 方法来唤醒该 Condition 等待队列中的线程,而不会影响其他 Condition 的等待队列和其中的线程。

这样的设计使得线程间的协作更加灵活和精细,可以根据具体的业务逻辑和需求,为不同的条件和场景创建专门的 Condition 对象及其等待队列,从而更好地控制线程的等待和唤醒,提高并发程序的正确性和性能。

例如,在一个线程池的实现中,可能有一个 Condition 用于等待任务队列中有任务可执行,另一个 Condition 用于等待线程池中的空闲线程数量达到一定阈值等,它们各自管理着不同的等待线程,互不干扰。

  1. 公平锁情况

    1. 同步队列

      • 当同步队列中的线程被唤醒时(例如前面的线程释放了锁),线程会按照先进先出(FIFO)的原则依次尝试获取锁。被唤醒的线程会检查自己是否是同步队列中的第一个节点(头节点的下一个节点),如果是,它会尝试通过CAS操作来修改state变量以获取锁。这是因为公平锁要保证线程获取锁的顺序是按照请求锁的顺序来的,所以会严格按照队列顺序来处理。

      • 如果CAS操作成功,线程就获取到了锁,然后从同步队列中移除该节点;如果CAS操作失败,说明可能有新的线程插队(这种情况在公平锁中一般是由于一些特殊的实现细节或者并发干扰导致的),被唤醒的线程会再次等待,直到轮到自己成为第一个节点并且成功通过CAS获取锁。

    2. 等待队列

      • 当等待队列中的线程被唤醒(通过ConditionsignalsignalAll操作)后,线程会先重新获取ReentrantLock锁。由于公平锁的特性,它会加入到同步队列的尾部,然后按照同步队列的规则,等待自己成为头节点的下一个节点后,再尝试通过CAS操作获取锁。这样就保证了公平性,即等待条件满足的线程也需要按照请求锁的顺序来获取锁。

  2. 非公平锁情况

    1. 同步队列

      • 当同步队列中的线程被唤醒时,和公平锁类似,它会尝试通过CAS操作修改state变量来获取锁。但是与公平锁不同的是,非公平锁允许新到达的线程直接尝试获取锁,而不考虑同步队列中的顺序。所以在同步队列中的线程获取锁时,可能会被新到达的线程抢先获取锁。如果被抢先,被唤醒的线程会继续等待,直到再次有机会通过CAS获取锁。

    2. 等待队列

      • 当等待队列中的线程被唤醒后,它会直接尝试通过CAS操作获取锁,而不是先加入到同步队列的尾部。如果CAS操作成功,线程就获取到了锁并继续执行;如果CAS操作失败,说明锁已经被其他线程获取(可能是新到达的线程或者同步队列中抢先的线程),那么该线程会加入到同步队列的尾部,等待下一次获取锁的机会。这体现了非公平锁的“非公平”特性,即等待条件满足的线程也可能会被其他新到达的线程抢先获取锁。

面试题:Java中的 Synchronized 是怎么实现的

分析:

原理,修饰不同地方的解释,依赖于什么(Monitor,Monitor中的属性),使用 synchronized 的早期流程,后期的锁升级

回答:

原理:

synchronzied 实现原理基于 JVM 的 Monitor(监视器锁)机制,每个对象的对象头中都有一个 MarkWord 部分,锁状态不同,MarkWord 存的数据也不同,重量级锁状态存的是指向 monitor 的指针

修饰不同地方的解释:

synchronized 可以修饰方法和修饰代码块,修饰普通方法锁住的是当前实例对象,修饰静态方法锁住的是当前对象,修饰代码块可以锁主任意对象(看括号中是什么),不管是修饰代码还是代码块,在字节码层面,JVM 会插入两条指令,monitorenter 用于获取 monitor 锁,monitorexit 用于释放锁

依赖于什么:

Monitor 中有几个重要属性:owner,entryList,waitSet

Owner 表示当前持有锁的线程,当一个线程获取锁失败时会进入 entryList,当一个线程调用 wait() 时 该线程会释放锁并进入waitSet

Synchronized 的 monitor 锁主要使用的是 owner 和 entryList,waitSet 主要是 synchronized 配合 wait/notify/notifyAll 使用

使用 synchronized 的早期流程:

早期使用 synchronized 锁时,线程获取锁的过程:

检查锁状态 -> 获取不到锁时的处理 -> 锁的释放

  1. 检查锁状态:检查当前对象的 MatkWord 来判断锁的状态,如果锁是无锁状态,那么线程会获取锁,将 monitor 中的 owner 设置为当前线程

  2. 如果没有获取到锁:那么线程会进入 Monitor 的 EntryList 等待

  3. 线程执行完临界区代码后,会释放锁,并将 monitor 中的 owner 设置为 null。当锁被释放时,EntryList 中的线程会竞争锁。

后期的锁升级:

JDK6 开始引入了偏向锁和轻量级锁,避免每次都要加 monitor 这样的重量级锁

具体的锁升级流程是:无锁 -> 偏向锁 -> 轻量锁 -> 重量锁(monitor)

  1. 无锁:一开始没有线程持有锁

  2. 偏向锁:当只有一个线程访问时,JVM 会将锁设置为偏向锁

  3. 轻量级锁:当有其他不同线程来竞争锁时(相同线程不会升级,会进行重入),会使用 CAS 来获取锁,如果成功,就获取到了轻量级锁,CAS 不成功,就会升级重量级锁。

  4. 重量级锁:轻量级锁阶段的 CAS 不成功,JVM 会将锁升级为重量级锁,也就是 monitor 锁

当 Java 的 synchronized 升级到重量级锁后,所有线程都释放锁了,此时它还是重量级锁吗?

回答:重量级锁的线程释放后,锁重新从无锁开始,此时如果再有一个线程争抢锁,则会从轻量级锁开始

Synchronized 配合 wait() notify() notifyAll() 的使用:

此时 WaitSet 中有多个线程,我现在调用了一个 notifyAll(),那么所有在 WaitSet 中的线程会被唤醒,并从 WaitSet 中移除。被唤醒的线程会尝试获取锁:

  • 如果成功获取锁,线程会继续执行

  • 如果未能获取锁,线程会进入 EntryList 等待(不回 waitSet 了,因为等待条件已经满足,只是没拿到锁,所以要进 entryList)

为什么 wait() notify() notifyAll() 得在同步代码块里?

分析:因为 wait() notify() notifyAll() 得获取锁之后才能使用,所以得放在同步代码块里

回答:

当一个线程需要调用对象的wait()方法的时候,这个线程必须拥有该对象的锁,接着它就会释放这个对象锁并进入等待状态直到其他线程调用这个对象上的notify()方法。同样的,当一个线程需要调用对象的notify()方法时,也会先获取到对象的锁,然后执行notify,最后再释放这个对象的锁,以便其他在等待的线程就可以得到这个对象锁。由于所有的这些方法都需要线程持有对象的锁,这样就只能通过同步来实现,所以它们只能在同步方法或者同步块中被调用。

面试题:Synchronized 和 ReentrantLock 的区别

分析:

5 个不同:用法,获取释放锁时机不同,锁类型不同,响应中断不同,底层实现不同

Synchronized 和 ReentrantLock 都是可重入锁

回答:

Synchronized 和 ReentrantLock 都是 Java 中提供的可重入锁,主要区别如下:

  • 用法不同:synchronized 可以用来修饰普通方法,静态方法和代码块,而 ReentrantLock 只能用于代码块

  • 获取锁和释放锁的时机不同:synchronized 自动加锁和释放锁,而 ReentrantLock 需要手动加锁和释放锁

  • 锁类型不同:synchronized 是非公平锁,而 ReentrantLock 默认为非公平锁,也可以手动指定为公平锁

  • 响应中断不同:ReentrantLock 可以响应中断,解决死锁问题 ,而 synchronized 不能响应中断

  • 底层实现不同:synchronized 是 JVM 层面通过监视器实现的,而 ReentrantLock 是经过 AQS 实现的

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2294909.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Nginx进阶篇 - nginx多进程架构详解

文章目录 1. nginx的应用特点2. nginx多进程架构2.1 nginx多进程模型2.2 master进程的作用2.3 进程控制2.4 worker进程的作用2.5 worker进程处理请求的过程2.6 nginx处理网络事件 1. nginx的应用特点 Nginx是互联网企业使用最为广泛的轻量级高性能Web服务器,其特点是…

【算法专场】分治(下)

目录 前言 归并排序 思想 912. 排序数组 算法思路 算法代码 LCR 170. 交易逆序对的总数 算法思路 算法代码 315. 计算右侧小于当前元素的个数 - 力扣(LeetCode) 算法思路 算法代码 493. 翻转对 算法思路 算法代码 好久不见~时隔多日&…

OSPF基础(2):数据包详解

OSPF数据包(可抓包) OSPF报文直接封装在IP报文中,协议号89 头部数据包内容: 版本(Version):对于OSPFv2,该字段值恒为2(使用在IPV4中);对于OSPFv3,该字段值恒为3(使用在IPV6中)。类型(Message Type):该OSPF报文的类型。…

Docker Desktop安装kubernetes时一直在Starting:Kubernetes failed to start

原因:由于墙的问题,导致拉取国外的K8s镜像失败 解决: 下载 k8s-for-docker-desktop 选中自己的kubernetes 版本 下载zip包 PowerShell运行load_images.ps1文件 重启docker kubernetes运行成功

StarSpider 星蛛 爬虫 Java框架 可以实现 lazy爬取 实现 HTML 文件的编译,子标签缓存等操作

StarSpider 星蛛 爬虫 Java框架 开源技术栏 StarSpider 能够实现 针对 HTML XSS SQL 数学表达式等杂乱数据的 爬取 解析 提取 需求! 目录 文章目录 StarSpider 星蛛 爬虫 Java框架目录介绍如何获取?maven配置 架构是什么样的?结果对象的类…

【翻译+论文阅读】DeepSeek-R1评测:粉碎GPT-4和Claude 3.5的开源AI革命

目录 一、DeepSeek-R1 势不可挡二、DeepSeek-R1 卓越之处三、DeepSeek-R1 创新设计四、DeepSeek-R1 进化之路1. 强化学习RL代替监督微调学习SFL2. Aha Moment “啊哈”时刻3. 蒸馏版本仅采用SFT4. 未来研究计划 部分内容有拓展,部分内容有删除,与原文会有…

动态规划LeetCode-121.买卖股票的最佳时机1

给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。 你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。 返回你可以从这笔交易中获取的最大利润。…

#渗透测试#批量漏洞挖掘#微商城系统 goods SQL注入漏洞

免责声明 本教程仅为合法的教学目的而准备,严禁用于任何形式的违法犯罪活动及其他商业行为,在使用本教程前,您应确保该行为符合当地的法律法规,继续阅读即表示您需自行承担所有操作的后果,如有异议,请立即停…

import { Component, Vue, Prop, Watch } from ‘vue-property-decorator‘

文章目录 导入部分的解释总结Vue 3 的推荐替代方案总结 你提供的代码片段是使用 vue-property-decorator 库的示例,这是一个第三方库,它提供了 Vue 组件的装饰器,使得编写类风格的 Vue 组件更加方便。以下是对代码中每个部分的详细解释&…

X Window System 架构概述

X Window System 架构概述 1. X Server 与 X Client ​ 这里引入一张维基百科的图,在Linux系统中,若用户需要图形化界面,则可以使用X Window System,其使用**Client-Server**架构,并通过网络传输相关信息。 ​ ​ X…

【ArcGIS Pro 简介1】

ArcGIS Pro 是由 Esri (Environmental Systems Research Institute)公司开发的下一代桌面地理信息系统(GIS)软件,是传统 ArcMap 的现代化替代产品。它结合了强大的空间分析能力、直观的用户界面和先进的三维可视化技术…

启明星辰发布MAF大模型应用防火墙产品,提升DeepSeek类企业用户安全

2月7日,启明星辰面向DeepSeek等企业级大模型业务服务者提供的安全防护产品——天清MAF(Model Application Firewall)大模型应用防火墙产品正式发布。 一个新赛道将被开启…… DeepSeek的低成本引爆赛道规模 随着DeepSeek成为当前最热的现象级…

小米AI眼镜官微上线,将与小米15 Ultra同台亮相,近屿智能用心培育 AI 人才

近日,小米眼镜官微已正式上线,认证主体为小米通讯技术有限公司。据悉,小米AI眼镜已获得入网许可,并计划提前至2月发布,与小米15 Ultra同台亮相。 此前,小米AI眼镜原定于2025年3月至4月发布。早在去年&#…

Mac下使用brew安装go 以及遇到的问题

首先按照网上找到的命令进行安装 brew install go 打开终端输入go version,查看安装的go版本 go version 配置环境变量 查看go的环境变量配置: go env 事实上安装好后的go已经可以使用了。 在home/go下新建src/hello目录,在该目录中新建…

在rtthread中,scons构建时,它是怎么知道是从rtconfig.h找宏定义,而不是从其他头文件找?

在rtthread源码中,每一个bsp芯片板级目录下都有一个 SConstruct scons构建脚本的入口, 在这里把rtthread tools/目录下的所有模块都添加到了系统路径中: 在tools下所有模块中,最重要的是building.py模块,在此脚本里面…

Unity游戏(Assault空对地打击)开发(7) 爆炸效果

效果 准备 首先请手搓一个敌军基地。 然后添加一个火焰特效插件或者自建。 爆炸脚本编写 新建一个脚本命名为Explode。 无需挂载到对象上。 首先是全部代码。 using System.Collections; using System.Collections.Generic; using System.Linq; using TMPro; using UnityEngine…

嵌入式面试题 C/C++常见面试题整理_7

一.什么函数不能声明为虚函数? 常见的不能声明为虚函数的有:普通函数(非成员函数):静态成员函数;内联成员函数;构造函数;友元函数。 1.为什么C不支持普通函数为虚函数?普通函数(非成员函数)只能被overload,不能被override,声明为虚函数也没有什么意思…

excel实用问题:提取文字当中的数字进行运算

0、前言: 这里汇总在使用excel工作过程中遇到的问题,excel使用wps版本,小规模数据我们自己提取数据可行,大规模数据就有些难受了,因此就产生了如下处理办法。 需求:需要把所有文字当中的数字提取出来&…

【prompt实战】AI +OCR技术结合ChatGPT能力项目实践(BOL提单识别提取专家)

本文原创作者:姚瑞南 AI-agent 大模型运营专家,先后任职于美团、猎聘等中大厂AI训练专家和智能运营专家岗;多年人工智能行业智能产品运营及大模型落地经验,拥有AI外呼方向国家专利与PMP项目管理证书。(转载需经授权) 目录 1. 需求背景 2. 目标 3. BOL通用处理逻辑…

昇思打卡营第五期(MindNLP特辑)番外:硅基流动 x 华为云DeepSeek V3 API推理MindTinyRAG

1.前言 前脚,DeepSeek面临的巨头企业官宣加入vs多国政府下场质疑的冰火两重天局势尚未平静(DeepSeek在美两重天:五大巨头接入,政府诚惶诚恐);后脚,OpenAI被逼急,凌晨亮出全新推理…