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
。这是一个权衡简洁性、安全性和功能灵活性的选择。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
counta和count的区别
counta和count的区别

Count函数用于计算指定范围内数字的个数,而CountA函数用于计算指定范围内非空单元格的个数。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

203

2023.11.20

go语言 面向对象
go语言 面向对象

本专题整合了go语言面向对象相关内容,阅读专题下面的文章了解更多详细内容。

57

2025.09.05

java面向对象
java面向对象

本专题整合了java面向对象相关内容,阅读专题下面的文章了解更多详细内容。

62

2025.11.27

python如何计算数的阶乘
python如何计算数的阶乘

方法:1、使用循环;2、使用递归;3、使用math模块;4、使用reduce函数。更多详细python如何计算数的阶乘的内容,可以阅读下面的文章。

177

2023.11.13

python求阶乘教程大全
python求阶乘教程大全

本专题整合了python求阶乘相关教程,阅读专题下面的文章了解更多详细内容。

13

2025.11.08

python语言求阶乘
python语言求阶乘

本专题整合了python中阶乘相关教程,阅读专题下面的文章了解更多详细步骤。

43

2025.12.06

线程和进程的区别
线程和进程的区别

线程和进程的区别:线程是进程的一部分,用于实现并发和并行操作,而线程共享进程的资源,通信更方便快捷,切换开销较小。本专题为大家提供线程和进程区别相关的各种文章、以及下载和课程。

743

2023.08.10

Python 多线程与异步编程实战
Python 多线程与异步编程实战

本专题系统讲解 Python 多线程与异步编程的核心概念与实战技巧,包括 threading 模块基础、线程同步机制、GIL 原理、asyncio 异步任务管理、协程与事件循环、任务调度与异常处理。通过实战示例,帮助学习者掌握 如何构建高性能、多任务并发的 Python 应用。

375

2025.12.24

Rust内存安全机制与所有权模型深度实践
Rust内存安全机制与所有权模型深度实践

本专题围绕 Rust 语言核心特性展开,深入讲解所有权机制、借用规则、生命周期管理以及智能指针等关键概念。通过系统级开发案例,分析内存安全保障原理与零成本抽象优势,并结合并发场景讲解 Send 与 Sync 特性实现机制。帮助开发者真正理解 Rust 的设计哲学,掌握在高性能与安全性并重场景中的工程实践能力。

2

2026.03.05

热门下载

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

精品课程

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

共28课时 | 4.8万人学习

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

共34课时 | 5.4万人学习

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

共3课时 | 0.3万人学习

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

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