0

0

什么是可重入锁?为什么synchronized也是可重入的?

紅蓮之龍

紅蓮之龍

发布时间:2025-09-03 21:27:01

|

1072人浏览过

|

来源于php中文网

原创

可重入锁允许持有锁的线程重复获取同一把锁而不发生阻塞,synchronized和ReentrantLock均实现该特性。JVM通过监视器的持有者线程ID和计数器实现synchronized的可重入,线程首次获取锁时计数器为1,重入时递增,退出同步块时递减,归零后释放锁。ReentrantLock基于AQS框架,通过state变量和持有线程引用实现,支持公平锁、可中断获取、tryLock等高级功能。两者均避免自死锁,适用于递归调用、模块化设计等场景,synchronized更简洁安全,ReentrantLock在高竞争或需细粒度控制时更具优势。

什么是可重入锁?为什么synchronized也是可重入的?

可重入锁允许持有锁的线程在不释放锁的情况下,再次获取该锁。

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
块时:

  1. JVM会检查该监视器是否已经被持有。
  2. 如果未被持有,当前线程成为持有者,计数器设为1。
  3. 如果已被持有,JVM会进一步检查持有者是否就是当前线程。
  4. 如果是当前线程,计数器加1,允许线程继续执行。
  5. 如果不是当前线程,当前线程就会被阻塞,直到锁被释放。 当线程退出
    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)。如果没有可重入性,一个线程在进入一个同步方法后,如果该方法内部又调用了另一个需要相同锁的同步方法,线程就会尝试再次获取一个它自己已经持有的锁,从而导致永久阻塞,形成一个经典的自死锁。可重入锁机制优雅地解决了这个问题,确保了线程在自身操作上的流畅性。

常见的应用场景包括:

魔法映像企业网站管理系统
魔法映像企业网站管理系统

技术上面应用了三层结构,AJAX框架,URL重写等基础的开发。并用了动软的代码生成器及数据访问类,加进了一些自己用到的小功能,算是整理了一些自己的操作类。系统设计上面说不出用什么模式,大体设计是后台分两级分类,设置好一级之后,再设置二级并选择栏目类型,如内容,列表,上传文件,新窗口等。这样就可以生成无限多个二级分类,也就是网站栏目。对于扩展性来说,如果有新的需求可以直接加一个栏目类型并新加功能操作

下载
  1. 递归方法调用:如果一个递归方法本身是同步的,或者在递归过程中需要访问受保护的共享资源,可重入锁是不可或缺的。例如,一个计算阶乘的同步方法,在递归调用自身时,能够顺利地重入,而不会卡死。

    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;
        }
    }
  2. 封装与模块化设计:在面向对象设计中,一个类可能包含多个方法,它们都操作同一个内部状态,因此都需要对同一个对象进行同步。如果一个外部方法调用了内部方法,而内部方法也需要同步,可重入锁就保证了这种调用链的顺畅。它允许我们更自然地封装和组合同步逻辑,而无需担心因锁的重复获取而导致的阻塞。

  3. 框架与库的实现:许多Java并发框架和库在内部实现时,都依赖于可重入锁来保证其内部状态的一致性,同时允许调用者以直观的方式使用这些API,而不用担心底层锁的复杂性。

总的来说,可重入锁使得同步机制更加健壮和易于使用,它允许线程在已经拥有资源访问权限的前提下,继续进行与其相关的操作,这符合我们对“拥有权限”的直观理解。

synchronized
ReentrantLock
在可重入特性上的异同与选择考量

synchronized
ReentrantLock
都提供了可重入的锁机制,但它们在用法、功能和底层实现上有着显著的区别,这直接影响了我们在不同场景下的选择。

相同点:

  • 可重入性:这是它们最基本的共同点。两者都允许持有锁的线程再次获取该锁,避免了自死锁。
  • 保证原子性、可见性和有序性:作为锁,它们都能确保在多线程环境下对共享资源的访问是线程安全的。

不同点:

  1. 实现方式与控制粒度

    • synchronized
      :是Java语言的关键字,由JVM隐式管理。它的锁是基于对象的监视器(Monitor),自动加锁和释放锁。我们无法直接访问或控制锁的状态。
    • ReentrantLock
      :是
      java.util.concurrent.locks
      包下的一个类,它是一个显式锁。需要手动调用
      lock()
      方法获取锁,并在
      finally
      块中调用
      unlock()
      方法释放锁。这赋予了开发者更高的控制粒度。
  2. 功能扩展性

    • synchronized
      :功能相对单一,只支持最基本的互斥和可重入。无法实现尝试非阻塞获取锁、限时获取锁、公平锁、多个条件变量等高级功能。
    • ReentrantLock
      :提供了更丰富的功能。例如:
      • tryLock()
        :尝试获取锁,如果获取不到立即返回,避免阻塞。
      • tryLock(long timeout, TimeUnit unit)
        :在指定时间内尝试获取锁,超时则返回。
      • lockInterruptibly()
        :可响应中断的获取锁方式。
      • 公平性:可以构造公平锁(
        new ReentrantLock(true)
        ),按请求顺序获取锁,但性能通常低于非公平锁。
      • 条件变量(
        Condition
        :通过
        newCondition()
        方法可以创建多个条件变量,实现更精细的线程间协作(
        await()
        signal()/signalAll()
        )。
  3. 性能

    • 在早期Java版本中,
      synchronized
      的性能通常低于
      ReentrantLock
      。但随着JVM的不断优化(如偏向锁、轻量级锁、自旋锁),
      synchronized
      的性能已经得到了极大提升,在许多场景下甚至与
      ReentrantLock
      不相上下,甚至更好。对于简单的同步需求,
      synchronized
      通常表现优秀。
    • ReentrantLock
      在竞争激烈或需要高级功能的场景下,其性能优势和灵活性会更加明显。

选择考量:

  • 简洁性与习惯:如果同步需求简单,仅仅是保护一段代码或一个方法,
    synchronized
    无疑是更简洁、更直观的选择。它由JVM自动管理锁的释放,避免了忘记
    unlock()
    可能导致的死锁问题。这也是Java开发者最常用的同步机制。
  • 高级功能需求:当你需要更细粒度的控制,比如:
    • 尝试非阻塞地获取锁(避免长时间等待)。
    • 在特定条件下等待(使用多个
      Condition
      )。
    • 需要一个可中断的锁获取机制。
    • 对锁的公平性有明确要求。
    • 此时,
      ReentrantLock
      就是更好的选择。它的API设计使得这些高级并发模式的实现变得可能。
  • 避免死锁风险:使用
    ReentrantLock
    时,务必记住在
    finally
    块中释放锁,否则一旦同步块内发生异常,锁将永远不会被释放,导致严重的死锁。
    synchronized
    则没有这个风险,因为JVM会保证锁的正确释放。
  • 代码可读性与维护:对于简单的同步,
    synchronized
    的代码更紧凑,可读性高。
    ReentrantLock
    的代码会稍微冗长一些,但如果其带来的高级功能是必要的,那么这种额外的复杂性是值得的。

总而言之,对于大多数简单的互斥场景,

synchronized
是首选,因为它简单、安全且性能优异。只有当
synchronized
无法满足特定需求时,才考虑使用
ReentrantLock
。这是一个权衡简洁性、安全性和功能灵活性的选择。

相关专题

更多
java
java

Java是一个通用术语,用于表示Java软件及其组件,包括“Java运行时环境 (JRE)”、“Java虚拟机 (JVM)”以及“插件”。php中文网还为大家带了Java相关下载资源、相关课程以及相关文章等内容,供大家免费下载使用。

825

2023.06.15

java正则表达式语法
java正则表达式语法

java正则表达式语法是一种模式匹配工具,它非常有用,可以在处理文本和字符串时快速地查找、替换、验证和提取特定的模式和数据。本专题提供java正则表达式语法的相关文章、下载和专题,供大家免费下载体验。

724

2023.07.05

java自学难吗
java自学难吗

Java自学并不难。Java语言相对于其他一些编程语言而言,有着较为简洁和易读的语法,本专题为大家提供java自学难吗相关的文章,大家可以免费体验。

728

2023.07.31

java配置jdk环境变量
java配置jdk环境变量

Java是一种广泛使用的高级编程语言,用于开发各种类型的应用程序。为了能够在计算机上正确运行和编译Java代码,需要正确配置Java Development Kit(JDK)环境变量。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

395

2023.08.01

java保留两位小数
java保留两位小数

Java是一种广泛应用于编程领域的高级编程语言。在Java中,保留两位小数是指在进行数值计算或输出时,限制小数部分只有两位有效数字,并将多余的位数进行四舍五入或截取。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

398

2023.08.02

java基本数据类型
java基本数据类型

java基本数据类型有:1、byte;2、short;3、int;4、long;5、float;6、double;7、char;8、boolean。本专题为大家提供java基本数据类型的相关的文章、下载、课程内容,供大家免费下载体验。

445

2023.08.02

java有什么用
java有什么用

java可以开发应用程序、移动应用、Web应用、企业级应用、嵌入式系统等方面。本专题为大家提供java有什么用的相关的文章、下载、课程内容,供大家免费下载体验。

428

2023.08.02

java在线网站
java在线网站

Java在线网站是指提供Java编程学习、实践和交流平台的网络服务。近年来,随着Java语言在软件开发领域的广泛应用,越来越多的人对Java编程感兴趣,并希望能够通过在线网站来学习和提高自己的Java编程技能。php中文网给大家带来了相关的视频、教程以及文章,欢迎大家前来学习阅读和下载。

16861

2023.08.03

php源码安装教程大全
php源码安装教程大全

本专题整合了php源码安装教程,阅读专题下面的文章了解更多详细内容。

7

2025.12.31

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Django 教程
Django 教程

共28课时 | 2.6万人学习

麻省理工大佬Python课程
麻省理工大佬Python课程

共34课时 | 5万人学习

MySQL 初学入门(mosh老师)
MySQL 初学入门(mosh老师)

共3课时 | 0.3万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号