synchronized 的可重入性:避免死锁的隐藏武器

摘要

synchronized 关键字具备 可重入性 (Reentrancy),同一线程在持有锁的情况下,可以再次获取同一把锁而不会阻塞。本文将从概念、代码示例、JVM 实现机制和工程实践四个方面,深入解析 synchronized 的可重入性。


正文

一、什么是可重入性?

可重入性(Reentrant)是指 同一线程在持有锁时,可以重复进入被同一个锁保护的临界区,而不会发生死锁。

换句话说:如果锁是"可重入"的,那么一个线程在进入某个 synchronized 方法/代码块后,还能继续调用另一个需要同一把锁的 synchronized 方法。


二、代码示例

1. 方法递归调用
java 复制代码
public class ReentrantDemo {
    public synchronized void methodA() {
        System.out.println(Thread.currentThread().getName() + " 进入 methodA");
        methodB(); // 再次请求同一个锁
    }

    public synchronized void methodB() {
        System.out.println(Thread.currentThread().getName() + " 进入 methodB");
    }

    public static void main(String[] args) {
        ReentrantDemo demo = new ReentrantDemo();
        new Thread(demo::methodA, "T1").start();
    }
}

结果:线程 T1 先进入 methodA,随后 不会阻塞 ,而是直接进入 methodB,因为这是同一线程在请求同一把对象锁。

2. 父子类继承中的可重入性
java 复制代码
class Parent {
    public synchronized void doSomething() {
        System.out.println("父类方法执行");
    }
}

class Child extends Parent {
    @Override
    public synchronized void doSomething() {
        System.out.println("子类方法执行");
        super.doSomething(); // 依然是同一把锁
    }
}

结果:子类方法调用父类方法不会造成死锁,因为锁是可重入的。


三、JVM 如何实现可重入性?

在 JVM 中,synchronized 是基于 对象头中的 Monitor(监视器锁) 实现的。

Monitor 内部维护了一个 计数器(recursions) ,用于记录线程重入的次数。

  • 第一次获取锁:计数器从 0 → 1,线程成为锁的拥有者。
  • 同一线程再次获取锁:计数器 +1。
  • 退出同步块/方法:计数器 -1。
  • 计数器归零:锁才真正释放,其他线程才能获取。

这就是为什么 synchronized 能避免 同一线程自我阻塞 的根本原因。


四、可重入性的优势

  1. 避免死锁
  • 如果没有可重入性,线程在调用 methodA() 的时候进入 methodB() 就会死锁。
  1. 提升编程灵活性
  • 允许在继承、递归调用中安全地使用 synchronized
  1. 降低锁使用的复杂度
  • 开发者无需手动判断当前线程是否持有锁,简化了并发编程模型。

五、工程实践建议

  1. 理解可重入,但不要滥用
  • 可重入不是性能优化手段,而是一种安全保证。
  • 不要因为"可重入"就随意嵌套锁,仍需控制临界区大小。
  1. 注意递归调用的深度
  • 虽然可重入避免了死锁,但无限递归依然可能导致栈溢出。
  1. 对比 ReentrantLock
  • ReentrantLock 也支持可重入,并提供了更多功能(可中断、公平锁、条件队列)。
  • 在需要灵活控制时,可以考虑替代 synchronized

六、总结

synchronized 的可重入性是并发编程中的重要特性:

  • 同一线程可以重复进入锁保护的代码,不会造成自我死锁;
  • 底层通过 Monitor 的计数器 实现;
  • 在递归、继承等复杂场景下尤为重要。

理解可重入性,可以帮助我们在设计并发程序时写出更加 安全、简洁、易维护 的代码。

相关推荐
笨蛋不要掉眼泪1 分钟前
Java虚拟机:堆的参数配置
java·开发语言·jvm
swipe3 分钟前
05|(前端转后全栈)不手写一堆 SQL,后端怎么操作数据库?MyBatis-Plus 入门
前端·后端·全栈
霸道流氓气质4 分钟前
SpringBoot中使用JasperReports 报表引擎 — 介绍、原理与使用实践
java·spring boot·后端
swipe7 分钟前
04|(前端转后全栈)前端状态为什么不够用?从页面数据到 MySQL 持久化
前端·后端·全栈
花生了什么事o9 分钟前
DDD 分层架构:六层分层架构
java·架构·ddd
苏三说技术10 分钟前
为什么越来越多人使用WebFlux?
后端
武子康26 分钟前
Ask/Allow 不是安全边界:企业 Coding Agent 必须建立四层治理(Policy / Scoped Credential / Sandbox / Provenance)
人工智能·后端·agent
Wang's Blog33 分钟前
Go-Zero项目开发17: IM私聊功能实现与消息存储设计
开发语言·后端·golang
她的男孩43 分钟前
一行配置,把 Spring Boot 后台变成 AI Agent 的工具箱:Forge MCP Server 插件源码拆解
人工智能·后端