可重入锁是什么?synchronized为何可重入
大家好,今天本人给大家带来文章《可重入锁是什么?synchronized为何是可重入的》,文中内容主要涉及到,如果你对文章方面的知识点感兴趣,那就请各位朋友继续看下去吧~希望能真正帮到你们,谢谢!
可重入锁允许持有锁的线程重复获取同一把锁而不发生阻塞,synchronized和ReentrantLock均实现该特性。JVM通过监视器的持有者线程ID和计数器实现synchronized的可重入,线程首次获取锁时计数器为1,重入时递增,退出同步块时递减,归零后释放锁。ReentrantLock基于AQS框架,通过state变量和持有线程引用实现,支持公平锁、可中断获取、tryLock等高级功能。两者均避免自死锁,适用于递归调用、模块化设计等场景,synchronized更简洁安全,ReentrantLock在高竞争或需细粒度控制时更具优势。

可重入锁允许持有锁的线程在不释放锁的情况下,再次获取该锁。synchronized关键字实现的锁就是一种典型的可重入锁,这意味着当一个线程已经获得了某个对象的锁,它就可以继续进入该对象其他或相同的同步代码块,而不会发生死锁。JVM内部会追踪线程获取锁的次数。
解决方案
可重入锁的核心在于,它识别当前尝试获取锁的线程是否就是已经持有该锁的线程。如果是同一个线程,它就可以“重新进入”临界区,而不会被阻塞。这种机制避免了线程因为自己持有锁而无法再次获取锁,从而导致死锁的尴尬局面。
具体来说,当一个线程首次获取可重入锁时,锁的计数器会加一。如果同一个线程再次尝试获取该锁,计数器会再次加一,并且锁仍然由该线程持有。只有当计数器归零时,锁才会被完全释放,其他等待的线程才有机会获取它。
synchronized关键字在Java中就是通过这种机制工作的。当你使用synchronized(this)或synchronized方法时,JVM会为当前对象(或类)关联一个监视器(Monitor)。当一个线程进入同步块时,它会尝试获取这个监视器。如果该线程已经持有这个监视器,它就可以直接进入,监视器的内部计数器会递增。当线程退出同步块时,计数器会递减。只有当计数器减到零时,监视器才会被完全释放。
举个例子,假设你有一个方法methodA是同步的,它又调用了另一个同步方法methodB,而methodB又调用了methodC。如果这三个方法都同步在同一个对象上,那么持有锁的线程可以从methodA顺利进入methodB,再进入methodC,而不会因为尝试获取一个自己已经持有的锁而被阻塞。
public class ReentrantExample {
public synchronized void outerMethod() {
System.out.println(Thread.currentThread().getName() + " 进入 outerMethod");
innerMethod();
System.out.println(Thread.currentThread().getName() + " 退出 outerMethod");
}
public synchronized void innerMethod() {
System.out.println(Thread.currentThread().getName() + " 进入 innerMethod");
// 可以在这里调用更深层次的同步方法
// deepInnerMethod();
System.out.println(Thread.currentThread().getName() + " 退出 innerMethod");
}
public static void main(String[] args) {
ReentrantExample example = new ReentrantExample();
new Thread(() -> example.outerMethod(), "Thread-1").start();
}
}在这个例子中,Thread-1进入outerMethod时获取了example对象的锁,然后它又能顺利地进入innerMethod,因为innerMethod也是同步在同一个example对象上的。这就是synchronized可重入性的直接体现。
深入理解可重入锁的实现原理与内部机制
可重入锁的实现,无论是synchronized还是java.util.concurrent.locks.ReentrantLock,其核心都在于两个关键要素:锁的持有者(Owner)和获取计数(Acquisition Count)。
对于synchronized而言,这一切都由JVM底层自动完成。每个Java对象在内存中都关联着一个监视器(Monitor),这个监视器在概念上包含了锁的持有者线程ID和一个计数器。当一个线程尝试进入synchronized块时:
- JVM会检查该监视器是否已经被持有。
- 如果未被持有,当前线程成为持有者,计数器设为1。
- 如果已被持有,JVM会进一步检查持有者是否就是当前线程。
- 如果是当前线程,计数器加1,允许线程继续执行。
- 如果不是当前线程,当前线程就会被阻塞,直到锁被释放。
当线程退出
synchronized块时,计数器减1。只有当计数器减到0时,监视器才会被完全释放,其他等待的线程才可能获取到它。这个过程是透明的,我们开发者无需手动管理。
而对于ReentrantLock,它的实现则更为显式和灵活,它基于Java的抽象队列同步器(AbstractQueuedSynchronizer, AQS)框架。AQS内部维护了一个state变量来表示锁的获取状态(这就是我们的计数器),以及一个指向当前持有锁的线程的引用。
- 当一个线程调用
lock()方法时:- 它会尝试原子性地将
state从0变为1。如果成功,当前线程就成为锁的持有者。 - 如果
state不为0,它会检查当前持有锁的线程是否就是自己。 - 如果是自己,就将
state加1,实现重入。 - 如果不是自己,线程就会被封装成一个节点,加入到AQS的等待队列中,并被park(挂起)。
- 它会尝试原子性地将
- 当线程调用
unlock()方法时:state减1。- 如果
state减到0,表示锁完全释放,当前线程将不再是持有者,并会唤醒等待队列中的下一个线程。
这种设计使得ReentrantLock不仅支持可重入,还能在此基础上提供公平性选择、尝试非阻塞获取锁(tryLock())以及条件变量(Condition)等高级功能,这些是synchronized无法直接提供的。
可重入锁在多线程编程中的核心价值与应用场景
可重入锁的存在,极大地简化了多线程编程中对共享资源的访问控制,避免了许多潜在的死锁和逻辑复杂性。其核心价值在于它允许“信任”当前持有锁的线程,让其能够自由地调用其他需要相同锁的同步方法或代码块。
最直接的价值体现在避免自死锁(Self-Deadlock)。如果没有可重入性,一个线程在进入一个同步方法后,如果该方法内部又调用了另一个需要相同锁的同步方法,线程就会尝试再次获取一个它自己已经持有的锁,从而导致永久阻塞,形成一个经典的自死锁。可重入锁机制优雅地解决了这个问题,确保了线程在自身操作上的流畅性。
常见的应用场景包括:
递归方法调用:如果一个递归方法本身是同步的,或者在递归过程中需要访问受保护的共享资源,可重入锁是不可或缺的。例如,一个计算阶乘的同步方法,在递归调用自身时,能够顺利地重入,而不会卡死。
public class FactorialCalculator { private int result; public synchronized int calculate(int n) { if (n <= 1) { result = 1; return 1; } // 递归调用自身,再次获取锁,因为是可重入的,所以不会死锁 int temp = n * calculate(n - 1); result = temp; // 假设需要同步更新结果 return temp; } }封装与模块化设计:在面向对象设计中,一个类可能包含多个方法,它们都操作同一个内部状态,因此都需要对同一个对象进行同步。如果一个外部方法调用了内部方法,而内部方法也需要同步,可重入锁就保证了这种调用链的顺畅。它允许我们更自然地封装和组合同步逻辑,而无需担心因锁的重复获取而导致的阻塞。
框架与库的实现:许多Java并发框架和库在内部实现时,都依赖于可重入锁来保证其内部状态的一致性,同时允许调用者以直观的方式使用这些API,而不用担心底层锁的复杂性。
总的来说,可重入锁使得同步机制更加健壮和易于使用,它允许线程在已经拥有资源访问权限的前提下,继续进行与其相关的操作,这符合我们对“拥有权限”的直观理解。
synchronized 与 ReentrantLock 在可重入特性上的异同与选择考量
synchronized和ReentrantLock都提供了可重入的锁机制,但它们在用法、功能和底层实现上有着显著的区别,这直接影响了我们在不同场景下的选择。
相同点:
- 可重入性:这是它们最基本的共同点。两者都允许持有锁的线程再次获取该锁,避免了自死锁。
- 保证原子性、可见性和有序性:作为锁,它们都能确保在多线程环境下对共享资源的访问是线程安全的。
不同点:
实现方式与控制粒度:
synchronized:是Java语言的关键字,由JVM隐式管理。它的锁是基于对象的监视器(Monitor),自动加锁和释放锁。我们无法直接访问或控制锁的状态。ReentrantLock:是java.util.concurrent.locks包下的一个类,它是一个显式锁。需要手动调用lock()方法获取锁,并在finally块中调用unlock()方法释放锁。这赋予了开发者更高的控制粒度。
功能扩展性:
synchronized:功能相对单一,只支持最基本的互斥和可重入。无法实现尝试非阻塞获取锁、限时获取锁、公平锁、多个条件变量等高级功能。ReentrantLock:提供了更丰富的功能。例如:tryLock():尝试获取锁,如果获取不到立即返回,避免阻塞。tryLock(long timeout, TimeUnit unit):在指定时间内尝试获取锁,超时则返回。lockInterruptibly():可响应中断的获取锁方式。- 公平性:可以构造公平锁(
new ReentrantLock(true)),按请求顺序获取锁,但性能通常低于非公平锁。 - 条件变量(
Condition):通过newCondition()方法可以创建多个条件变量,实现更精细的线程间协作(await()和signal()/signalAll())。
性能:
- 在早期Java版本中,
synchronized的性能通常低于ReentrantLock。但随着JVM的不断优化(如偏向锁、轻量级锁、自旋锁),synchronized的性能已经得到了极大提升,在许多场景下甚至与ReentrantLock不相上下,甚至更好。对于简单的同步需求,synchronized通常表现优秀。 ReentrantLock在竞争激烈或需要高级功能的场景下,其性能优势和灵活性会更加明显。
- 在早期Java版本中,
选择考量:
- 简洁性与习惯:如果同步需求简单,仅仅是保护一段代码或一个方法,
synchronized无疑是更简洁、更直观的选择。它由JVM自动管理锁的释放,避免了忘记unlock()可能导致的死锁问题。这也是Java开发者最常用的同步机制。 - 高级功能需求:当你需要更细粒度的控制,比如:
- 尝试非阻塞地获取锁(避免长时间等待)。
- 在特定条件下等待(使用多个
Condition)。 - 需要一个可中断的锁获取机制。
- 对锁的公平性有明确要求。
- 此时,
ReentrantLock就是更好的选择。它的API设计使得这些高级并发模式的实现变得可能。
- 避免死锁风险:使用
ReentrantLock时,务必记住在finally块中释放锁,否则一旦同步块内发生异常,锁将永远不会被释放,导致严重的死锁。synchronized则没有这个风险,因为JVM会保证锁的正确释放。 - 代码可读性与维护:对于简单的同步,
synchronized的代码更紧凑,可读性高。ReentrantLock的代码会稍微冗长一些,但如果其带来的高级功能是必要的,那么这种额外的复杂性是值得的。
总而言之,对于大多数简单的互斥场景,synchronized是首选,因为它简单、安全且性能优异。只有当synchronized无法满足特定需求时,才考虑使用ReentrantLock。这是一个权衡简洁性、安全性和功能灵活性的选择。
到这里,我们也就讲完了《可重入锁是什么?synchronized为何可重入》的内容了。个人认为,基础知识的学习和巩固,是为了更好的将其运用到项目中,欢迎关注golang学习网公众号,带你了解更多关于JVM,synchronized,reentrantlock,可重入锁,AQS的知识点!
番茄小说换背景色教程详解
- 上一篇
- 番茄小说换背景色教程详解
- 下一篇
- Pythongroupby数据聚合技巧详解
-
- 文章 · java教程 | 26秒前 |
- Java代理模式原理与应用解析
- 287浏览 收藏
-
- 文章 · java教程 | 14分钟前 |
- Java接口实现多继承方法解析
- 186浏览 收藏
-
- 文章 · java教程 | 36分钟前 | Java网络编程 超时设置 指数退避 SocketTimeoutException 重连策略
- Java捕获SocketTimeoutException及重连方法
- 327浏览 收藏
-
- 文章 · java教程 | 37分钟前 |
- JavaProperties读取配置方法
- 295浏览 收藏
-
- 文章 · java教程 | 1小时前 | 环境变量 jdk java-version javac-version Java环境验证
- Java安装后怎么检查环境是否配置成功
- 402浏览 收藏
-
- 文章 · java教程 | 1小时前 | 缓冲区 JavaNIO BufferOverflowException BufferUnderflowException flip()
- Java缓冲异常处理方法解析
- 351浏览 收藏
-
- 文章 · java教程 | 1小时前 |
- Java对象序列化保存方法详解
- 355浏览 收藏
-
- 文章 · java教程 | 1小时前 |
- 读写锁特性解析与实际应用
- 264浏览 收藏
-
- 文章 · java教程 | 1小时前 |
- JavaSemaphore限流实现与高并发优化
- 226浏览 收藏
-
- 文章 · java教程 | 1小时前 |
- 数据表格列冻结问题及解决方法
- 498浏览 收藏
-
- 文章 · java教程 | 1小时前 |
- 原子类底层原理深度解析
- 254浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- ChatExcel酷表
- ChatExcel酷表是由北京大学团队打造的Excel聊天机器人,用自然语言操控表格,简化数据处理,告别繁琐操作,提升工作效率!适用于学生、上班族及政府人员。
- 3178次使用
-
- Any绘本
- 探索Any绘本(anypicturebook.com/zh),一款开源免费的AI绘本创作工具,基于Google Gemini与Flux AI模型,让您轻松创作个性化绘本。适用于家庭、教育、创作等多种场景,零门槛,高自由度,技术透明,本地可控。
- 3390次使用
-
- 可赞AI
- 可赞AI,AI驱动的办公可视化智能工具,助您轻松实现文本与可视化元素高效转化。无论是智能文档生成、多格式文本解析,还是一键生成专业图表、脑图、知识卡片,可赞AI都能让信息处理更清晰高效。覆盖数据汇报、会议纪要、内容营销等全场景,大幅提升办公效率,降低专业门槛,是您提升工作效率的得力助手。
- 3418次使用
-
- 星月写作
- 星月写作是国内首款聚焦中文网络小说创作的AI辅助工具,解决网文作者从构思到变现的全流程痛点。AI扫榜、专属模板、全链路适配,助力新人快速上手,资深作者效率倍增。
- 4523次使用
-
- MagicLight
- MagicLight.ai是全球首款叙事驱动型AI动画视频创作平台,专注于解决从故事想法到完整动画的全流程痛点。它通过自研AI模型,保障角色、风格、场景高度一致性,让零动画经验者也能高效产出专业级叙事内容。广泛适用于独立创作者、动画工作室、教育机构及企业营销,助您轻松实现创意落地与商业化。
- 3797次使用
-
- 提升Java功能开发效率的有力工具:微服务架构
- 2023-10-06 501浏览
-
- 掌握Java海康SDK二次开发的必备技巧
- 2023-10-01 501浏览
-
- 如何使用java实现桶排序算法
- 2023-10-03 501浏览
-
- Java开发实战经验:如何优化开发逻辑
- 2023-10-31 501浏览
-
- 如何使用Java中的Math.max()方法比较两个数的大小?
- 2023-11-18 501浏览

