可重入锁 介绍以及原理

博客介绍了可重入锁的概念,指出当线程请求自己持有的对象锁时,重入锁请求会成功。以Synchronized为例,通过代码证明其是可重入锁,同一线程对同一对象锁可多次重入。还阐述了可重入锁的实现原理,即锁关联线程持有者和计数器,根据计数器状态控制锁的获取和释放。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

可重入锁介绍

首先,在Synchronized和ReentrantLock的区别中,我们介绍了两者都是可重入锁,那么究竟什么是可重入锁呢?其内部原理又是如何实现的呢?

概念:可重入锁意味着:若一个程序或子程序可以“在任意时刻被中断然后操作系统调度执行另外一段代码,这段代码又调用了该子程序不会出错”,则称其为可重入(reentrant或re-entrant)的。即当该子程序正在运行时,执行线程可以再次进入并执行它,仍然获得符合设计时预期的结果。与多线程并发执行的线程安全不同,可重入强调对单个线程执行时重新进入同一个子程序仍然是安全的。

通俗来说:当线程请求一个由其它线程持有的对象锁时,该线程会阻塞,而当线程请求由自己持有的对象锁时,如果该锁是重入锁,请求就会成功,否则阻塞。

Synchronized是可重入锁,why?

我们先来看一段代码:

public class Xttblog extends SuperXttblog {
    public static void main(String[] args) {
        Xttblog child = new Xttblog();
        child.doSomething();
    }
 
    public synchronized void doSomething() {
        System.out.println("child.doSomething()" + Thread.currentThread().getName());
        doAnotherThing(); // 调用自己类中其他的synchronized方法
    }
 
    private synchronized void doAnotherThing() {
        super.doSomething(); // 调用父类的synchronized方法
        System.out.println("child.doAnotherThing()" + Thread.currentThread().getName());
    }
}
 
class SuperXttblog {
    public synchronized void doSomething() {
        System.out.println("father.doSomething()" + Thread.currentThread().getName());
    }
}


//运行结果:
//child.doSomething()Thread-5492
//father.doSomething()Thread-5492
//child.doAnotherThing()Thread-5492

上述代码就证明Synchronized是可重入锁(这些方法输出了相同的线程名称,表明即使递归使用synchronized也没有发生死锁,证明其是可重入的)。

每个synchronized都获取的是同一个对象,这里的对象锁只有一个,就是 child 对象的锁,当执行 child.doSomething 时,该线程获得 child 对象的锁,在 doSomething 方法内执行 doAnotherThing 时再次请求child对象的锁,因为synchronized 是重入锁,所以可以得到该锁,继续在 doAnotherThing 里执行父类的 doSomething 方法时第三次请求 child 对象的锁,同样可得到。如果不是重入锁的话,那这后面这两次请求锁将会被一直阻塞,从而导致死锁。

所以在 java 内部,同一线程在调用自己类中其他 synchronized 方法/块或调用父类的 synchronized 方法/块都不会阻碍该线程的执行。就是说同一线程对同一个对象锁是可重入的,而且同一个线程可以获取同一把锁多次,也就是可以多次重入。因为java线程是基于“每线程(per-thread)”,而不是基于“每调用(per-invocation)”的(java中线程获得对象锁的操作是以线程为粒度的,per-invocation 互斥体获得对象锁的操作是以每调用作为粒度的)

 

可重入锁的实现原理?

看到这里,你终于明白了 synchronized 是一个可重入锁。但是面试官要再问你,可重入锁的原理是什么?

 

重入锁实现可重入性原理或机制是:每一个锁关联一个线程持有者和计数器,当计数器为 0 时表示该锁没有被任何线程持有,那么任何线程都可能获得该锁而调用相应的方法;当某一线程请求成功后,JVM会记下锁的持有线程,并且将计数器置为 1;此时其它线程请求该锁,则必须等待;而该持有锁的线程如果再次请求这个锁,就可以再次拿到这个锁,同时计数器会递增;当线程退出同步代码块时,计数器会递减,如果计数器为 0,则释放该锁。

转自:https://www.cnblogs.com/incognitor/p/9894604.html

### Redission 可重入分布式的实现机制 #### 背景介绍 在分布式环境中,为了防止多个节点同时操作共享资源而导致数据不一致的问题,通常需要引入一种同步控制机制——分布式。Redission 是 Redis 官方推荐的一种高效、易用的分布式解决方案[^1]。 #### 可重入的概念 可重入是指同一个线程可以多次获取同一把而不发生死的情况。这种特性允许线程在其已经持有的内部再次申请该,而不会因为重复加导致阻塞。Redission 的 `RLock` 接口实现了这一功能,并通过 Redis 的原子性和 Lua 脚本保障其一致性[^4]。 --- #### Redission 可重入的核心实现原理 ##### 1. 的状态管理 Redission 使用 Redis 中的 Hash 数据结构来存储的信息。Hash 结构中的字段如下: - **Key**: 表示的存在与否。 - **Field (小 Key)**: 存储持有的客户端唯一标识符(通常是线程 ID 或会话 ID)。 - **Value**: 记录的持有次数(即重入计数器)。每次线程重新进入时,此值都会增加;当线程退出时,此值减少直到变为零,则释放[^3]。 ##### 2. 加流程 以下是 Redission 获取可重入的主要逻辑: ```java // 尝试获取 boolean isLock = lock.tryLock(1, 10, TimeUnit.SECONDS); ``` - 当某个线程调用 `tryLock()` 方法时,Redission 首先会在 Redis 上创建一个带有过期时间的键值对作为。 - 如果当前线程已经是的拥有者(即 Field 对应的 Value 已存在),则直接更新重入计数器并延长的有效期。 - 否则,尝试设置新的记录。如果成功,则初始化重入计数器为 1。 Lua 脚本用于确保整个过程的原子性,避免因网络延迟或其他异常情况引发的竞争条件。 ##### 3. 解流程 解的过程同样依赖于 Lua 脚本来完成: ```java lock.unlock(); ``` - 线程在调用 `unlock()` 方法时,首先检查自己是否是的实际持有者。 - 若确实是持有者,则将对应的重入计数器减一。 - 如果计数器降为零,则删除的相关记录,正式释放。 需要注意的是,只有真正的持有者才能执行解鎖动作,其他线程即使知道的名字也无法非法解除他人拥有的。 --- #### 技术细节补充 - **超时保护** Redission 设定了一个合理的默认超时期限,在超过该期限后未续期的情况下自动释放,从而避免死的发生。 - **高可用支持** 利用了 Redis Sentinel 或 Cluster 模式下的选举算法,使得即便部分 Redis 实例宕机也不会影响整体服务正常运行[^2]。 - **公平性与性能权衡** Redission 并未严格遵循 FIFO 的排队策略,而是优先考虑效率和吞吐量。因此,在极端负载下可能会出现轻微的不公平现象。 --- ### 总结 综上所述,Redission 的可重入分布式基于 Redis 的高性能特性和 Lua 脚本的一致性保障,提供了一种简单可靠的方式解决跨进程间的资源共享冲突问题。它的设计既兼顾了灵活性又不失安全性,非常适合现代微服务架构的需求。 ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值