当前位置: 首页 > article >正文

可重入与可重入锁:多线程编程中的安全卫士

一、什么是可重入(Reentrant)?

1. 核心定义

可重入(Reentrant) 是指一段代码(如函数、方法或锁)在 同一线程 中被多次调用时,不会因重复访问而导致错误或死锁的特性。可重入性是线程安全的重要基础。

2. 可重入的常见场景

  • 递归函数:函数直接或间接调用自身。

  • 嵌套锁:同一线程多次获取同一把锁。

  • 回调函数:在持有锁的情况下触发回调,回调中再次请求同一锁。

3. 示例:递归函数

public class Factorial {
    // 可重入的递归函数
    public synchronized int factorial(int n) {
        if (n <= 1) return 1;
        return n * factorial(n - 1); // 递归调用自身
    }
}
  • 若锁不可重入,第二次调用 factorial(n-1) 时会被阻塞,导致死锁。


二、什么是可重入锁(Reentrant Lock)?

1. 核心定义

可重入锁 是一种允许同一线程多次获取同一把锁的同步机制。每次获取锁时,锁的计数器加1;释放锁时,计数器减1。只有当计数器归零时,锁才被完全释放。

2. 可重入锁的实现原理

  • 持有线程标识:记录当前持有锁的线程。

  • 计数器:统计同一线程的加锁次数。

  • 公平性策略(可选):决定锁的获取顺序(公平或非公平)。

3. 可重入锁的典型实现

(1) Java 的 ReentrantLock
ReentrantLock lock = new ReentrantLock();

public void methodA() {
    lock.lock();
    try {
        methodB(); // 嵌套调用
    } finally {
        lock.unlock();
    }
}

public void methodB() {
    lock.lock(); // 同一线程可重入
    try {
        // 业务逻辑
    } finally {
        lock.unlock();
    }
}
(2) Java 的 synchronized 关键字

public synchronized void methodA() {
    methodB(); // 嵌套调用
}

public synchronized void methodB() {
    // 业务逻辑
}

三、可重入锁解决了什么问题?

1. 避免自死锁(Self-Deadlock)

  • 问题场景:若锁不可重入,线程在持有锁的情况下再次请求该锁会被阻塞,导致死锁。

  • 示例

    public class NonReentrantExample {
        public synchronized void methodA() {
            methodB(); // 若锁不可重入,此处会阻塞
        }
        public synchronized void methodB() {
            // 业务逻辑
        }
    }

  • 可重入锁的解决:允许同一线程多次加锁,避免阻塞。

2. 支持递归调用

  • 递归函数需要多次访问同一资源,可重入锁确保递归逻辑的正确性。

3. 简化嵌套锁设计

  • 在复杂业务逻辑中,多个方法可能嵌套调用并共享同一锁,可重入锁简化了代码设计。


四、可重入锁 vs 不可重入锁

特性可重入锁不可重入锁
同一线程多次加锁允许(计数器累加)阻塞或死锁
实现复杂度较高(需维护线程标识和计数器)较低
典型应用场景递归、嵌套方法调用简单临界区保护
性能稍低(需额外状态管理)较高

五、可重入锁的底层实现(以 Java 为例)

1. ReentrantLock 的计数器实现


public class ReentrantLock implements Lock {
    private final Sync sync;

    abstract static class Sync extends AbstractQueuedSynchronizer {
        // 记录持有锁的线程和计数器
        final Thread getOwner() {
            return getState() == 0 ? null : getExclusiveOwnerThread();
        }
        final int getHoldCount() {
            return isHeldExclusively() ? getState() : 0;
        }
    }
}
  • 计数器:通过 state 字段实现,每次加锁 state++,解锁 state--

  • 线程标识:通过 exclusiveOwnerThread 字段记录当前持有锁的线程。

2. synchronized 的锁升级机制

  • 偏向锁:首次获取锁时标记线程ID。

  • 轻量级锁:通过CAS竞争锁。

  • 重量级锁:竞争激烈时升级为操作系统级互斥锁(Mutex)。


六、可重入锁的最佳实践

1. 始终在 finally 块中释放锁

ReentrantLock lock = new ReentrantLock();
lock.lock();
try {
    // 业务逻辑
} finally {
    lock.unlock(); // 确保锁被释放
}

2. 避免锁的过度嵌套

  • 嵌套层级过多会增加锁的持有时间,降低并发性能。

3. 优先使用 ReentrantLock 的高级功能

  • 可中断锁lock.lockInterruptibly()

  • 超时获取锁lock.tryLock(long timeout, TimeUnit unit)

  • 公平锁new ReentrantLock(true)


七、总结

1. 可重入锁的核心价值

  • 解决同一线程多次加锁的死锁问题。

  • 支持递归和嵌套调用,提升代码灵活性。

2. 适用场景

  • 多线程环境下的复杂业务逻辑。

  • 需要递归或嵌套访问共享资源的场景。

3. 选择建议

  • 默认情况下优先使用 synchronized(简洁高效)。

  • 需要高级功能(如超时、公平性)时选择 ReentrantLock

通过合理使用可重入锁,开发者可以显著降低多线程编程中的死锁风险,构建更健壮的并发系统


http://www.kler.cn/a/561344.html

相关文章:

  • rust学习笔记6-数组练习704. 二分查找
  • MySQL数据,查询QPS,TPS 数据
  • 二分查找理解
  • xss-lab
  • 计算机视觉算法实战——异常检测(主页有源码)
  • OpenGL 03--顶点着色器、片段着色器、元素缓冲对象
  • 刷题日记5
  • 【TVM教程】为 NVIDIA GPU 自动调度神经网络
  • 【STM32】使用电打火器测试火焰传感器,去掉传感器LED依然亮
  • 使用torch.compile进行CPU优化
  • .NET Core MVC IHttpActionResult 设置Headers
  • IDEA-插件开发踩坑记录-第五坑-没有飞机场导致无法访问GITHUB导致的讨厌问题
  • 【深度学习神经网络学习笔记(一)】深度学习介绍
  • 在vite上使用tailwindcss详细教程(不报错)
  • [C++][cmake]使用C++部署yolov12目标检测的tensorrt模型支持图片视频推理windows测试通过
  • Go语言--语法基础3--下载安装--Linux基础操作命令
  • 图神经网络:拓扑数据分析的新时代
  • DeepSeek AI智能运营:重构企业效率的范式革命
  • kafka数据拉取和发送
  • BUU41 [GYCTF2020]FlaskApp1【SSTI】