Synchronized和ReentrantLock面试详解
前言
接下来为大家带来的是 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 争抢锁
一、等待队列中线程的唤醒顺序
默认情况:在
ReentrantLock的Condition实现中,如果没有特别指定,当条件满足时,唤醒等待队列中的线程顺序是不确定的。它通常是基于底层实现的一些规则,可能与线程进入等待队列的顺序没有直接的固定关联。例如,可能是根据底层数据结构(如链表)的遍历顺序或者其他内部机制来决定先唤醒哪个线程。
signal()和signalAll()的影响:
当调用
condition.signal()方法时,通常只会唤醒等待队列中的一个线程,但具体是哪个线程被唤醒是不确定的,可能是任意一个等待线程。而调用
condition.signalAll()方法时,会唤醒等待队列中的所有线程,这些线程会竞争获取锁,获取到锁的线程可以继续执行,其他线程则会进入同步队列等待获取锁的机会。二、
Condition与等待队列的关系
一个
Condition一个等待队列:每个Condition对象都有自己独立的等待队列。这意味着不同的Condition可以用于不同的等待条件和线程协作场景,并且它们各自管理自己的等待线程队列。
例如,在一个复杂的多线程应用中,可能有多个不同的条件需要线程等待,如数据准备好、资源可用、某个标志位设置等。可以为每个这样的条件创建一个
Condition对象,每个Condition的等待队列中存放着等待相应条件的线程。当某个条件满足时,通过对应的Condition对象的signal()或signalAll()方法来唤醒该Condition等待队列中的线程,而不会影响其他Condition的等待队列和其中的线程。这样的设计使得线程间的协作更加灵活和精细,可以根据具体的业务逻辑和需求,为不同的条件和场景创建专门的
Condition对象及其等待队列,从而更好地控制线程的等待和唤醒,提高并发程序的正确性和性能。例如,在一个线程池的实现中,可能有一个
Condition用于等待任务队列中有任务可执行,另一个Condition用于等待线程池中的空闲线程数量达到一定阈值等,它们各自管理着不同的等待线程,互不干扰。
公平锁情况
同步队列:
当同步队列中的线程被唤醒时(例如前面的线程释放了锁),线程会按照先进先出(FIFO)的原则依次尝试获取锁。被唤醒的线程会检查自己是否是同步队列中的第一个节点(头节点的下一个节点),如果是,它会尝试通过CAS操作来修改
state变量以获取锁。这是因为公平锁要保证线程获取锁的顺序是按照请求锁的顺序来的,所以会严格按照队列顺序来处理。如果CAS操作成功,线程就获取到了锁,然后从同步队列中移除该节点;如果CAS操作失败,说明可能有新的线程插队(这种情况在公平锁中一般是由于一些特殊的实现细节或者并发干扰导致的),被唤醒的线程会再次等待,直到轮到自己成为第一个节点并且成功通过CAS获取锁。
等待队列:
当等待队列中的线程被唤醒(通过
Condition的signal或signalAll操作)后,线程会先重新获取ReentrantLock锁。由于公平锁的特性,它会加入到同步队列的尾部,然后按照同步队列的规则,等待自己成为头节点的下一个节点后,再尝试通过CAS操作获取锁。这样就保证了公平性,即等待条件满足的线程也需要按照请求锁的顺序来获取锁。非公平锁情况
同步队列:
当同步队列中的线程被唤醒时,和公平锁类似,它会尝试通过CAS操作修改
state变量来获取锁。但是与公平锁不同的是,非公平锁允许新到达的线程直接尝试获取锁,而不考虑同步队列中的顺序。所以在同步队列中的线程获取锁时,可能会被新到达的线程抢先获取锁。如果被抢先,被唤醒的线程会继续等待,直到再次有机会通过CAS获取锁。等待队列:
当等待队列中的线程被唤醒后,它会直接尝试通过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 锁时,线程获取锁的过程:
检查锁状态 -> 获取不到锁时的处理 -> 锁的释放
-
检查锁状态:检查当前对象的 MatkWord 来判断锁的状态,如果锁是无锁状态,那么线程会获取锁,将 monitor 中的 owner 设置为当前线程
-
如果没有获取到锁:那么线程会进入 Monitor 的 EntryList 等待
-
线程执行完临界区代码后,会释放锁,并将 monitor 中的 owner 设置为 null。当锁被释放时,EntryList 中的线程会竞争锁。
后期的锁升级:
JDK6 开始引入了偏向锁和轻量级锁,避免每次都要加 monitor 这样的重量级锁
具体的锁升级流程是:无锁 -> 偏向锁 -> 轻量锁 -> 重量锁(monitor)
-
无锁:一开始没有线程持有锁
-
偏向锁:当只有一个线程访问时,JVM 会将锁设置为偏向锁
-
轻量级锁:当有其他不同线程来竞争锁时(相同线程不会升级,会进行重入),会使用 CAS 来获取锁,如果成功,就获取到了轻量级锁,CAS 不成功,就会升级重量级锁。
-
重量级锁:轻量级锁阶段的 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 实现的
相关文章:
Synchronized和ReentrantLock面试详解
前言 接下来为大家带来的是 Java 中的两个典型锁代表:Synchronized 和 ReentrantLock 的详解 面试题:谈一谈AQS 在说 ReentrantLock 时,有必要先了解一下 AQS,因为 ReentrantLock 就是基于 AQS 实现的 分析: 共享…...
1.2 学习驱动(Driver)分为几步?
文章目录 前言一、什么是UVM中的驱动(Driver)?二、如何理解Driver?三、如何使用Driver?第一步:定义Driver类第二步:实现run_phase任务第三步:实现驱动任务第四步:实例化和…...
【MySQL篇】事务的认识以及四大特性
何为事务? 事务(Transaction)是指一组操作的集合,这些操作要么全部执行成功,要么全部不执行。事务通常用于保证数据库的一致性、完整性和可靠性,确保数据的完整性与正确性。 有效避免部分执行,…...
2.7日学习总结
深入探究栈、队列与二叉树 一、栈的深度剖析 进阶特性:除了常规的入栈、出栈操作,栈在处理函数调用、表达式求值等场景时,展现出独特的递归模拟能力。利用栈可以将递归算法转化为非递归形式,有效避免因递归过深导致的栈溢出问题。…...
SQL带外注入
SQL 带外注入(Out-of-Band SQL Injection, OOB SQLi) 是 SQL 注入的一种特殊类型,主要用于以下情况: 数据库没有直接返回错误信息(比如被防火墙拦截了)。无法使用常规注入手法(如 UNION、错误信…...
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报文的类型。…...
ubuntu直接运行arm环境qemu-arm-static
qemu-arm-static 嵌入式开发有时会在ARM设备上使用ubuntu文件系统。开发者常常会面临这样一个问题,想预先交叉编译并安装一些应用程序,但是交叉编译的环境配置以及依赖包的安装十分繁琐,并且容易出错。想直接在目标板上进行编译和安装&#x…...
Docker Desktop安装kubernetes时一直在Starting:Kubernetes failed to start
原因:由于墙的问题,导致拉取国外的K8s镜像失败 解决: 下载 k8s-for-docker-desktop 选中自己的kubernetes 版本 下载zip包 PowerShell运行load_images.ps1文件 重启docker kubernetes运行成功...
beyond the ‘PHYSICAL‘ memory limit.问题处理
Container [pid5616,containerIDcontainer_e50_1734408743176_3027740_01_000006] is running 507887616B beyond the ‘PHYSICAL’ memory limit. Current usage: 4.5 GB of 4 GB physical memory used; 6.6 GB of 8.4 GB virtual memory used. Killing container. 1.增大map…...
AI大模型零基础学习(1):大模型使用篇
一、大模型是什么?为什么你需要它? 一句话理解:大模型像一个能听懂人话的"超级智能助手",它能写文章、解数学题、翻译语言、写代码…只要你会打字提问,它就能给出答案。 典型场景: 学生党&…...
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. 未来研究计划 部分内容有拓展,部分内容有删除,与原文会有…...
先进制造aps专题二十八 生产排程仿真引擎和工厂生产仿真引擎的设计
一 排产仿真引擎的设计 主要分为仿真模型,仿真模型逻辑和仿真框架这三个部分 1 仿真模型 和算法排产不一样,在算法排产里,机器对应的是数据库记录,排产逻辑是写在整体的算法里的,而仿真排产,机器对应的是…...
WPF模板
WPF模板深度解析:打造个性化UI的利器 在WPF(Windows Presentation Foundation)的世界里,模板(Template)是构建个性化用户界面(UI)不可或缺的工具。它们允许开发者将控件的逻辑功能与…...
动态规划LeetCode-121.买卖股票的最佳时机1
给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。 你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。 返回你可以从这笔交易中获取的最大利润。…...
pytest+request+yaml+allure 接口自动化测试全解析[手动写的跟AI的对比]
我手动写的:Python3:pytest+request+yaml+allure接口自动化测试_request+pytest+yaml-CSDN博客 AI写的:pytest+request+yaml+allure 接口自动化测试全解析 在当今的软件开发流程中,接口自动化测试扮演着至关重要的角色。它不仅能够提高测试效率,确保接口的稳定性和正确性…...
单片机通讯中的时序图:初学者的入门指南
一、什么是时序图? 在单片机的世界里,时序图是一种非常重要的工具,它用于描述信号在时间上的变化规律。简单来说,时序图就像是信号的“时间线”,它展示了各个信号线在不同时间点上的电平状态。通过时序图,我…...
#渗透测试#批量漏洞挖掘#微商城系统 goods SQL注入漏洞
免责声明 本教程仅为合法的教学目的而准备,严禁用于任何形式的违法犯罪活动及其他商业行为,在使用本教程前,您应确保该行为符合当地的法律法规,继续阅读即表示您需自行承担所有操作的后果,如有异议,请立即停…...
Lua中文语言编程源码-第十一节,其它小改动汉化过程
__tostring 汉化过程 liolib.c metameth[] {"__转换为字符串", f_tostring}, lauxlib.c luaL_callmeta(L, idx, "__转换为字符串") lua.c luaL_callmeta(L, 1, "__转换为字符串") __len 汉化过程 ltm.c luaT_eventname[] ltablib.c c…...
import { Component, Vue, Prop, Watch } from ‘vue-property-decorator‘
文章目录 导入部分的解释总结Vue 3 的推荐替代方案总结 你提供的代码片段是使用 vue-property-decorator 库的示例,这是一个第三方库,它提供了 Vue 组件的装饰器,使得编写类风格的 Vue 组件更加方便。以下是对代码中每个部分的详细解释&…...
C++基础系列【5】namespace using
本文主要介绍namespace和using。 什么是namespace? namespace是指命名空间,表示某个变量标识符的可见空间,比如下面的代码: namespace Meow {int k 100; }int main() {std::cout << k << std::endl; }这段代码中在…...
MySQL万能备份脚本
此脚本适用于 MySQL 各个生命周期的版本 #!/bin/bash # mybackup.sh# 备份保留天数,建议保留三天 days7 # 备份时间 time$(date %Y%m%d%H%M%S) # 备份保存路径 backup_dir/opt/backup # 备份工具 toolmysqldump # 端口 port"3306" # 是否采用 --all-data…...
分桶函数的使用
除了 NTILE 函数,SQL 中还有其他一些与 分桶(bucketization)相关的函数,虽然它们的实现方式不同,但都涉及将数据分成多个区间或组。以下是一些常用的分桶函数: 1. CASE 语句 虽然 CASE 不是开窗函数&…...
5. k8s二进制集群之ETCD集群部署
下载etcd安装包创建etcd配置文件准备证书文件和etcd存储目录ETCD证书文件安装(分别对应指定节点)创建证书服务的配置文件启动etcd集群验证etcd集群状态继续上一篇文章《k8s二进制集群之ETCD集群证书生成》下面介绍一下etcd证书生成配置。 下载etcd安装包 https://github.com…...
JMeter通过BeanShell写入CSV文件中的中文乱码
在 JMeter 中通过 BeanShell 写入 CSV 文件时,如果出现中文乱码问题,通常是因为文件编码不匹配。默认情况下,FileWriter 使用的是系统默认编码(可能是 ISO-8859-1 或其他非 UTF-8 编码),而中文字符需要 UTF…...
智能化转型2.0:从“工具应用”到“价值重构”
过去几年,“智能化”从一个模糊的概念逐渐成为企业发展的核心议题。2024年,随着生成式AI、大模型、智能体等技术的爆发式落地,中国企业正式迈入智能化转型的2.0时代。这一阶段的核心特征是从单一场景的“工具应用”转向全链条的“价值重构”&…...
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 的现代化替代产品。它结合了强大的空间分析能力、直观的用户界面和先进的三维可视化技术…...
