什么是可重入锁,如何实现可重入锁?

可重入锁(Reentrant Lock)是一种特殊的线程同步机制,它允许同一个线程在已经持有该锁的情况下,可以多次成功获取同一把锁,而不会因为等待自己释放锁而导致死锁。

在 Java 中,synchronized 关键字和 java.util.concurrent.locks.ReentrantLock 类都是典型的可重入锁实现。

深度解析

原理/机制

可重入性的实现,核心在于为锁关联一个 持有线程标识 和一个 重入计数器。

  1. 初次加锁:当线程第一次请求锁时,JVM 或 ReentrantLock 会记录当前持有锁的线程,并将计数器设置为 1。
  2. 重入加锁:同一个线程再次请求该锁时,系统会检查请求线程是否为当前持有线程。如果是,则直接将计数器加 1,并立即成功 “获取” 锁(实际上并未阻塞)。
  3. 释放锁:线程每次调用 unlock() 或退出 synchronized 块时,计数器减 1。
  4. 完全释放:当计数器减到 0 时,锁才被真正释放,此时会清除持有线程标识,并唤醒其他等待该锁的线程。

这个机制避免了线程在递归调用、或一个同步方法调用另一个同步方法时,发生 “自己等自己” 的典型死锁场景。

代码示例

// 示例1:使用 synchronized(隐式可重入锁)演示递归重入
public class ReentrantDemo {
    public synchronized void outer() {
        System.out.println("进入 outer 方法,持有锁。");
        inner(); // 递归调用另一个同步方法
        System.out.println("退出 outer 方法。");
    }

    public synchronized void inner() {
        System.out.println("进入 inner 方法,成功重入锁。");
        // 如果锁不可重入,线程将在此处永久阻塞,等待自己释放锁(死锁)
    }

    public static void main(String[] args) {
        ReentrantDemo demo = new ReentrantDemo();
        demo.outer(); // 同一个线程,可以顺利执行
    }
}

// 示例2:使用 ReentrantLock(显式可重入锁)演示重入计数
import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockDemo {
    private final ReentrantLock lock = new ReentrantLock();

    public void performAction() {
        lock.lock(); // 第一次获取锁,计数=1
        try {
            System.out.println("首次加锁,重入计数: 1");
            reentrantAction(); // 调用另一个也需要同一把锁的方法
        } finally {
            lock.unlock(); // 最终释放,计数归零
        }
    }

    public void reentrantAction() {
        lock.lock(); // 第二次获取(重入),计数=2
        try {
            System.out.println("重入加锁,此时锁被同一个线程持有,计数递增。");
        } finally {
            lock.unlock(); // 释放一次,计数=1
        }
    }
}

对比分析与常见误区

  • 可重入锁 vs. 不可重入锁 | 特性 | 可重入锁 (如 synchronizedReentrantLock) | 不可重入锁 (简单自旋锁的基础形态) | | :--- | :--- | :--- | | 同一线程重入 | 允许,不会死锁 | 不允许,会导致死锁 | | 实现复杂度 | 较高,需维护线程ID和计数器 | 较低,只维护锁状态 | | 性能影响 | 略有开销(维护计数器) | 无此开销,但实用性低 | | 适用场景 | 通用场景,尤其是存在递归、回调或复杂同步方法调用的 OOP 环境 | 极少数特殊场景,或用于教学理解锁的基本概念 |

  • 常见误区

    1. 误区一:可重入等于高并发/高性能。 错。可重入性解决的是正确性问题(避免自死锁),它本身不直接提升并发性能。
    2. 误区二:synchronized 不是可重入锁。 错。这是最容易混淆的点。从 JDK 1.0 开始,synchronized 实现的监视器锁就是可重入的。
    3. 混淆“重入”与“并发”:可重入指的是 “同一个线程” 的重复进入,而非“多个线程”的并发访问。多个线程的并发安全仍需由锁的互斥性来保证。

最佳实践

  1. 优先使用内置机制:对于大多数同步场景,优先考虑使用 synchronized。JVM 会对其进行持续优化(如锁升级),且代码更简洁,不易出错(自动释放)。
  2. 按需选择 ReentrantLock:当需要尝试非阻塞获取锁(tryLock)、可中断的锁等待、公平锁策略、或绑定多个条件(Condition) 等高级功能时,再使用 ReentrantLock
  3. 确保释放:使用 ReentrantLock 时,必须在 finally 块中调用 unlock() 以确保锁在任何情况下都能被释放,防止死锁。
  4. 理解重入深度:虽然重入避免了死锁,但过深的递归重入(如 Bug 导致的无限递归)最终会引发 StackOverflowError,而非死锁。
posted @ 2026-04-16 10:16  DBA日记  阅读(25)  评论(0)    收藏  举报