可重入锁是什么?

本文详细探讨了Java中ReentrantLock的使用与优势,对比synchronized关键字,解析可重入锁的设计意义,不可重入锁的实现,并展示了如何将不可重入锁改造为可重入锁。

本文内容如有错误、不足之处,欢迎技术爱好者们一同探讨,在本文下面讨论区留言,感谢。

简介

可重入锁在Java中有synchronizeReentrantLock,其中synchronize是在JVM中进行可重入控制,ReentrantLock是在代码中实现可重入控制。

在Java 5.0中,增加了一个称为ReentrantLock的新功能,以增强内部锁定功能。在此之前,“synchronized" 和 "volatile”是实现并发的手段。

public synchronized void doAtomicTransfer(){
     //进入同步代码块 获取对此对象的锁定。
    operation1()
    operation2();    
} // 退出同步代码块, 释放(release)对此对象的锁定。

同步使用内部锁或监视器。Java中的每个对象都有一个与之关联的固有锁。每当线程尝试访问同步的块或方法时,它都会获取该对象的固有锁定或监视器。在使用静态方法的情况下,线程获取对类对象的锁定。就编写代码而言,内在锁定机制是一种干净的方法,对于大多数用例而言,它是相当不错的。

那么,为什么我们需要显式锁的附加功能?

内部锁定机制可能具有一些功能限制,例如:

  1. 无法中断等待获取锁的线程(lock Interruptibly)。
  2. 如果不愿意永远等待,就无法尝试获取锁(try lock)。
  3. 无法实现非块结构的锁定规则,因为必须在获取它们的同一块中释放固有锁。
ReentrantLock
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
.....

ReentrantLock

中断锁获取

可中断的锁获取允许在可取消的活动中使用锁。

该的lockInterruptibly方法可以让我们尝试,同时可中断获取锁。因此,基本上,它允许线程立即响应从另一个线程发送给它的中断信号。

例子:假设有一条共享线路来发送消息。希望以这样一种方式设计它:如果另一个线程来了并中断当前线程,则锁应释放并执行退出或关闭操作以取消当前任务。

public boolean sendOnSharedLine(String message) throws InterruptedException{
	lock.lockInterruptibly();
	try{
		return cancellableSendOnSharedLine(message);
	} finally {
		lock.unlock();
	}
}

private boolean cancellableSendOnSharedLine(String message){
.......
可重入设计的意义

广义上的可重入锁指的是可重复可递归调用的锁,在外层使用锁之后,在内层仍然可以使用,并且不发生死锁(前提得是同一个对象或者class),这样的锁就叫做可重入锁。

这样可以避免每次获取锁之后,在操作资源后,释放锁再获取锁,每次释放锁再获取锁的调度切换是很耗资源。

避免子类改写了父类的 synchronized 方法,调用子类时发生死锁。

不可重入锁
class UnReentantLock{
    private AtomicReference<Thread> owner = new AtomicReference<>();

    /**
     * 获取锁
     */
    public void lock(){
        Thread current = Thread.currentThread();
        // 自旋锁实现,同样可以使用do while
        //for (; ; ) {
        //    if (!owner.compareAndSet(null, current)) {
        //        return ;
        //    }
        //}
        do {

        } while (!owner.compareAndSet(null, current));
        return;
    }

    /**
     * 释放锁
     */
    public void unLock(){
        Thread current = Thread.currentThread();
        owner.compareAndSet(current, null);
    }
}

这里使用了原子引用和CAS

CAS的原理是Unsafe类和自旋锁,上面已经展示自旋锁如何使用,下面图片是Unsafe类中的一个native方法的c++实现,可以查看 JVM 本地方法栈(native method stack)解释
Unsafe方法内部实现

不可重入改为可重入

可重入锁实现是通过计数器来实现可重入多次。


ReentrantLock lock = new ReentrantLock();
lock.lock();
lock.lock();

try{
...
}finally{
	lock.unlock();
	lock.unlock();
}

上面的代码可以说明,加了几次lock()就要执行几次unlock();

下面是改造代码:
借鉴:https://blog.youkuaiyun.com/wuseyukui/article/details/72312574

class NumberReentrantLock{
    private AtomicReference<Thread> owner = new AtomicReference<>();
    private int lockNumber = 0;

    public void lock(){
        Thread current = Thread.currentThread();
        // 使用== 表示是同一个引用
        if (current == owner.get()) {
            lockNumber++;
            return;
        }
        do {

        } while (!owner.compareAndSet(null, current));
    }

    public void unlock(){
        Thread current = Thread.currentThread();
        if (current == owner.get()) {
            if (lockNumber == 0) {
                owner.compareAndSet(current, null);
            }else{
                lockNumber--;
            }
        }
    }
}
<think> 嗯,用户想了解Redisson可重入的作用。结合之前提供的引用资料,这个问题可以从分布式的基本需求切入。 用户引用的三份材料都指向可重入性这个核心特性,特别是引用[1]明确提到“允许线程多次请求同一个”,引用[3]则补充了Lua脚本的实现细节。看来用户需要的是对作用的具体阐释而非代码示例。 考虑到分布式环境的特殊性,应该突出两个维度:一是可重入性如何解决线程嵌套调用问题,比如在递归场景中避免死;二是自动续期机制在分布式环境中的必要性。用户提供的代码片段(引用[2])里tryLock方法的时间参数正好能佐证这点。 用户可能是个后端开发者,正在处理分布式系统资源竞争问题。ta的提问很精准,说明对的概念已有基础认知,但需要更深入地理解特定实现的价值。可以适当对比非可重入的缺陷来强化说明。 上次回答快速排序时采用了“核心功能+特点+应用”的结构效果不错,这次也可以沿用:先定义分布式的通用作用,再聚焦Redisson可重入特性的特殊价值,最后补充其在高可用场景下的优势。需要特别注意引用标注的规范性,每个技术点都要对应到用户提供的材料。 </think> Redisson可重入在分布式系统中的核心作用是**解决资源竞争的互斥性问题,同时支持线程安全地重复获取同一把**。其核心作用体现在以下方面: ### 🔒 1. 实现分布式互斥访问 在多节点、多线程的分布式环境中,确保共享资源(如数据库记录、文件、服务调用)在同一时刻只能被一个客户端的线程访问。例如: ```java RLock lock = redissonClient.getLock("orderLock"); lock.lock(); try { // 修改订单状态的业务逻辑(保证原子性) } finally { lock.unlock(); } ``` *作用:* 防止并发修改导致数据不一致[^1][^3]。 ### 🔁 2. 支持可重入性(关键特性) **核心作用**:允许同一线程多次获取同一把,避免死。典型场景包括: - **递归调用**:方法A获取后调用方法B,方法B也需同一 - **嵌套逻辑**:外层业务逻辑与内层子逻辑依赖同一资源 ```java public void methodA() { lock.lock(); try { methodB(); // 内层方法也需要同一 } finally { lock.unlock(); } } public void methodB() { lock.lock(); // 同一线程可再次获取(计数器+1) try { // 子逻辑 } finally { lock.unlock(); // 计数器-1,未归零时不释放 } } ``` *实现原理*:Redis中存储时记录线程ID和重入计数器(`HASH`结构)[^1][^3]。 ### ️ 3. 自动续期(Watchdog 机制) - **问题解决**:防止业务未执行完而超时释放 - **机制**:获取后启动后台线程,每隔(超时时间/3)自动续期 - **示例**:设超时为30秒,每10秒自动续期至30秒,直到业务完成或线程终止[^1][^2]。 ### 🌐 4. 高可用保障 通过Redis集群模式(如主从、哨兵、Cluster)实现: - 故障转移时状态持久化 - 避免单点故障导致失效 - 基于Redlock算法提升分布式一致性[^1]。 ### 💎 总结核心作用 | 作用 | 解决的问题 | 技术实现 | |---------------------|--------------------------|--------------------------| | 分布式互斥 | 资源竞争导致数据不一致 | Redis原子操作+SETNX | | 可重入性 | 嵌套调用死 | 线程ID+计数器(Redis HASH)| | 自动续期 | 业务阻塞导致超时释放 | Watchdog后台线程 | | 高可用 | 单点故障失效 | 集群部署+Redlock算法 | *** ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值