跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI 生图工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
Javajava

Java JUC 可重入锁详解:原理与死锁排查

Java JUC 中的可重入锁概念,涵盖 synchronized 关键字与 ReentrantLock 显式锁。解析了可重入锁的实现机理,包括 ObjectMonitor 计数器和线程持有关系。对比了隐式锁与显式锁的使用规范,并分析了死锁产生的原因、典型场景及通过 jps、jstack 工具进行排查的方法。

苹果系统发布于 2026/3/29更新于 2026/9/277 浏览

可重入锁(又名递归锁)

可重入锁是指指在同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁 (前提,锁对象得是同一个对象),不会因为之前已经获取过还没释放而阻塞。

如果是 1 个有 synchronized 修饰的递归调用方法,程序第 2 次进入被自己阻塞了岂不是天大的笑话,出现了作茧自缚。

所以 Java 中 ReentrantLock 和 synchronized 都是可重入锁,可重入锁的一个优点是可一定程度避免死锁。

可重入锁种类

  • 隐式锁 Synchronized synchronized 是 java 中的关键字,默认是可重入锁,即隐式锁

在同步块中

public class ReEntryLockDemo {
    public static void main(String[] args) {
        final Object objectLockA = new Object();
        new Thread(() -> {
            synchronized (objectLockA) {
                System.out.println("-----外层调用");
                synchronized (objectLockA) {
                    System.out.println("-----中层调用");
                    synchronized (objectLockA) {
                        System.out.println("-----内层调用");
                    }
                }
            }
        }, "a").start();
    }
}
//-----外层调用
//-----中层调用
//-----内层调用

在同步方法中

public class ReEntryLockDemo {
    public synchronized void m1() {
        // 指的是可重复可递归调用的锁,在外层使用之后,在内层仍然可以使用,并且不发生死锁,这样的锁就叫做可重入锁
        System.out.println(Thread.currentThread().getName() + "\t" + "-----come in m1");
        m2();
        System.out.println(Thread.currentThread().getName() + "\t-----end m1");
    }

    public synchronized void m2() {
        System.out.println("-----m2");
        m3();
    }

    public synchronized void m3() {
        System.out.println("-----m3");
    }

    public static void main(String[] args) {
        ReEntryLockDemo reEntryLockDemo = new ReEntryLockDemo();
        reEntryLockDemo.m1();
    }
}
/**
 * main -----come in m1
 * -----m2
 * -----m3
 * main -----end m1
 */

Synchronized 的重入实现机理

ObjectMonitor.hpp

140 行 ObjectMonitor(){ _header =NULL; _count =0;//用来记录该线程获取锁的次数 _waiters =0, _recursions =0;//锁的重入次数 _object =NULL; _owner =NULL;//------最重要的----指向持有 ObjectMonitor 对象的线程,记录哪个线程持有了我 _WaitSet =NULL;//存放处于 wait 状态的线程队列 _WaitSetLock =0; _Responsible =NULL; _succ =NULL; _cxq =NULL; FreeNext =NULL; _EntryList =NULL;//存放处于等待锁 block 状态的线程队列 _SpinFreq =0; _SpinClock =0; OwnerIsThread =0; _previous_owner_tid =0;}
  • ObjectMonitor.hpp 底层:每个锁对象拥有一个锁计数器和一个指向持有该锁的线程的指针。_count _owner
  • 首次加锁:当执行 monitorenter 时,如果目标锁对象的计数器为零,那么说明它没有被其他线程所持有,Java 虚拟机会将该锁对象的持有线程设置为当前线程,并且将其计数器加 1。
  • 重入:在目标锁对象的计数器不为零的情况下,如果锁对象的持有线程是当前线程,那么 Java 虚拟机可以将其计数器加 1,否则需要等待,直至持有线程释放该锁。
  • 释放锁:当执行 monitorexit 时,Java 虚拟机则需将锁对象的计数器减 1。计数器为零代表锁已被释放

显式锁 Lock

显式锁(即 Lock)也有 ReentrantLock 这样的可重入锁

感觉所谓的显式隐式即是指显示/隐式的调用锁

注意:lock unlock 要成对

public class ReEntryLockDemo {
    static Lock lock = new ReentrantLock();
    public static void main(String[] args) {
        {
            new Thread(() -> {
                lock.lock();
                try {
                    System.out.println(Thread.currentThread().getName() + "\t----come in 外层调用");
                    lock.lock();
                    try {
                        System.out.println(Thread.currentThread().getName() + "\t------come in 内层调用");
                    } finally {
                        lock.unlock();
                    }
                } finally {
                    lock.unlock();
                }
            }, "t1").start();
        }
    }
}
//t1 ----come in 外层调用
//t1 ------come in 内层调用

假如 lock unlock 不成对,单线程情况下问题不大,但多线程下出问题

public class ReEntryLockDemo {
    static Lock lock = new ReentrantLock();
    public static void main(String[] args) {
        new Thread(() -> {
            lock.lock();
            try {
                System.out.println(Thread.currentThread().getName() + "\t----come in 外层调用");
                lock.lock();
                try {
                    System.out.println(Thread.currentThread().getName() + "\t------come in 内层调用");
                } finally {
                    lock.unlock();
                }
            } finally {
                //lock.unlock();//-------------------------不成对 | 多线程情况
            }
        }, "t1").start();
        new Thread(() -> {
            lock.lock();
            try {
                System.out.println("t2 ----外层调用 lock");
            } finally {
                lock.unlock();
            }
        }, "t2").start();
    }
}
//t1 ----come in 外层调用
//t1 ------come in 内层调用
//(t2 ----外层调用 lock 假如不成对,这句话就不显示了)

死锁及排查

死锁是指两个或两个以上的线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力干涉那它们都将无法推进下去,如果系统资源充足,进程的资源请求都能够得到满足,死锁出现的可能性就很低,否则就会因争夺有限的资源而陷入死锁。 a 跟 b 两个资源互相请求对方的资源

死锁产生的原因

  • 系统资源不足
  • 进程运行推进的顺序不合适
  • 资源分配不当
public class DeadLockDemo {
    public static void main(String[] args) {
        Object object1 = new Object();
        Object object2 = new Object();
        new Thread(() -> {
            synchronized (object1) {
                System.out.println(Thread.currentThread().getName() + "\t 持有 a 锁,想获得 b 锁");
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //使得线程 b 也启动
                synchronized (object2) {
                    System.out.println(Thread.currentThread().getName() + "\t 成功获得 b 锁");
                }
            }
        }, "A").start();
        new Thread(() -> {
            synchronized (object2) {
                System.out.println(Thread.currentThread().getName() + "\t 持有 b 锁,想获得 a 锁");
                synchronized (object1) {
                    System.out.println(Thread.currentThread().getName() + "\t 成功获得 a 锁");
                }
            }
        }, "B").start();
    }
}

如何排查死锁

用命令

  • jps -l 查看当前进程运行状况
  • jstack 进程编号 查看该进程信息

小总结

指针指向 monitor 对象(也称为管程或监视器锁)的起始地址。每个对象都存在着一个 monitor 与之关联,当一个 monitor 被某个线程持有后,它便处于锁定状态。在 Java 虚拟机 (HotSpot) 中,monitor 是由 ObjectMonitor 实现的,其主要数据结构如下(位于 HotSpot 虚拟机源码 ObjectMonitor.hpp,C++ 实现的

更多推荐文章

查看全部
  • MCP 开发工具:npx 与 uvx 安装使用指南
  • OpenClaw 在 Mac 上本地化部署及接入飞书教程
  • CentOS 7 WinSCP 普通用户上传文件无权限解决方案
  • Gemini、ChatGPT、Qwen、豆包、Claude五大主流AI模型深度对比
  • Qwen3-32B 模型部署:使用 Clawdbot 网关实现 WebSocket 长连接
  • DeepSeek-R1 大模型基于 MS-Swift 框架部署推理与微调实践
  • 从互联网大厂到 AI 开发:几位职场人的转行经历与行业洞察
  • Python 核心语法实战:变量、流程控制与函数详解
  • 基于 AI 辅助的学生成绩综合统计分析系统开发实战
  • Ollama 故障排除与常见问题解决方案
  • GESP 2025 年 12 月二级 C++ T1 环保能量球
  • 二分查找算法进阶:山脉数组与旋转排序
  • OpenClaw Windows/Linux 安装指南
  • CCF-CSP 认证:机器人复健指南题解
  • Dify 工作流集成 Sambert-Hifigan 语音合成 API 实现对话机器人
  • CarelessWhisper:将非因果 Whisper 改造为低延迟流式模型
  • 为 AI 编程助手编写 Skills:一个前端开发者的实战笔记
  • LFM2.5-1.2B-Thinking-GGUF 模型:GGUF 格式与 llama.cpp 机制详解
  • 私有模型部署与企业知识库搭建:理论篇
  • Python 搭建具备记忆与人工干预的 Agent 机器人

相关免费在线工具

  • Keycode 信息

    查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online

  • Escape 与 Native 编解码

    JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online

  • JavaScript / HTML 格式化

    使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online

  • JavaScript 压缩与混淆

    Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online