Redis之Redisson的可重入锁原理及解读

 更新时间:2026年04月25日 16:52:15   作者:风吹迎面入袖凉  
这篇文章主要了Redisson可重入锁的概念和实现法,它通过网吧场景解释了什么是有锁和可重入,文中强调了要使同一把锁,以及必须成对unlock(),最后,它还强调了Redisson的计数器和锁的管理机制,作者还提供了实现步骤和注意事项

一. 什么是可重入锁?

假设你去网吧上网,网吧里的每台电脑(比如1号机)就是“一把锁”,开机密码(或身份证刷卡)就是“拿锁的权限”,你就是程序里的“线程”——只有你刷了卡、开了机,才能用这台电脑,别人不能用,这就是“锁”的作用(保证同一时间,只有一个线程能操作资源)。

场景1(可重入,正常情况):你刷身份证开机,用1号机(相当于“线程拿到锁”),玩了一会儿,想去厕所(相当于程序里“一个方法调用另一个方法,两个方法都需要同一把锁”),你不用关机、释放1号机(不用释放锁),直接起身去厕所,回来后还能直接坐回1号机、继续玩——这就是“可重入”,怎么拿、怎么用,不卡壳,不用等自己“释放”电脑,也不会卡死。

场景2(不可重入,卡死情况):如果这台电脑是“不可重入”的,你开机用了1号机后,想去厕所,必须先关机、释放1号机(释放锁),才能起身;等你从厕所回来,想再用1号机,还得重新刷卡开机(重新拿锁)。可你根本不想关机(业务还没做完),一旦关机,之前玩的内容就没了,不关机又没法起身——这就是程序里的“死锁”,程序直接卡死,没法继续运行。

对应到程序里,再简单说一句:你写了两个方法A和B,两个方法都需要同一把锁(相当于都需要用1号机),方法A里会调用方法B(相当于用1号机时,中途要做另一个需要用1号机的操作)。用Redisson可重入锁,程序执行A、再调用B时,能直接执行,不卡顿;用不可重入锁,程序会卡在B那里,自己等自己释放锁,永远等不到,直接卡死。

二、怎么实现的“重入”?

可重入锁主要用计数器实现的。计数器的唯一作用,就是记着你“拿了几次锁”,避免“释放一次就把锁彻底放走”,也避免自己卡死自己,全程由Redisson自动管理,我们不用手动操作。

具体工作流程:

  • 你第一次刷身份证,开机用1号机(线程第一次获取锁),Redisson的计数器就记1(相当于“你第一次占用1号机”);
  • 你玩到中途,想重启电脑(相当于程序里“方法A调用方法B,再次获取同一把锁”),因为是你本人(同一个线程),Redisson不用让你重新刷卡,直接把计数器加1,变成2(相当于“你第二次占用1号机”);
  • 重启完成,你继续玩(相当于methodB执行完),释放一次锁,计数器就减1,变成1(相当于“你释放了一次占用,但还在使用1号机”);
  • 你玩完了,关机离开(相当于methodA执行完),再释放一次锁,计数器减1,变成0(相当于“你彻底释放1号机”);
  • 只有计数器变成0,这把锁才真正放开,其他人才可以刷身份证、开机用1号机(其他线程才能获取这把锁)。

重点提醒:如果没有计数器,会出什么问题?

就像你在网吧,重启电脑(释放一次锁)后,计数器没记录,系统会误以为你“彻底离开”,直接把1号机释放,别人可能趁你还在座位上,就刷卡开机——对应程序里,就是“锁提前释放”,多个线程同时操作一个资源(比如同时改一个数据),会导致数据错乱、程序出问题。

补充一句:

Redisson已经帮我们把“计数器”“线程标识”(识别是不是同一个线程拿锁)全部封装好了,我们不用自己写一行代码管理计数器,只需要调用简单的lock()(拿锁)和unlock()(释放锁),就能实现可重入,这也是Redisson比其他Redis客户端好用的地方。

三、Redisson可重入锁怎么用?

第一步:准备环境

1. 给项目加依赖:

<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>3.23.3</version>
</dependency>

2. 配置Redis(复制到application.yml里,只改2个地方:Redis地址、Redis密码,没有密码就删掉密码那一行):

spring:
  redis:
    host: 127.0.0.1  # 改成你自己的Redis地址,本地开发就是127.0.0.1
    port: 6379       # 默认端口,一般不用改
    password: 123456 # 改成你自己的Redis密码,没有就删掉这一行
redisson:
  singleServerConfig:
    address: redis://127.0.0.1:6379  # 和上面的Redis地址、端口一致
    password: 123456 # 和上面的Redis密码一致,没有就删掉这一行

第二步:写核心代码

我们写一个服务类,里面有两个方法(methodA和methodB),methodA调用methodB,两个方法都用同一把锁,模拟“重入”场景(对应网吧里“开机后重启电脑”的场景),代码里的注释都写得很直白,一看就懂,直接复制粘贴到项目里就行:

import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
// 这个注解不用管,加了就能用
@Service
public class LockDemo {
    // 注入Redisson,不用管怎么来的,加了依赖就能自动注入
    @Autowired
    private RedissonClient redissonClient;
    // 锁的名字,必须唯一,相当于网吧1号机的“编号”,两个方法必须用同一个名字
    private static final String LOCK_NAME = "myLock";
    // 方法A:需要拿锁(相当于网吧开机用1号机)
    public void methodA() {
        // 1. 获取可重入锁(拿锁,就是刷身份证拿1号机的使用权)
        RLock lock = redissonClient.getLock(LOCK_NAME);
        try {
            // 2. 加锁(开机),不用管其他的,调用这个方法就好
            lock.lock();
            System.out.println("程序(线程)进入methodA,拿到锁了(相当于开机用1号机)");
            // 3. 调用methodB,methodB也需要同一把锁(相当于开机后重启电脑,再次占用1号机)
            methodB();
            // 模拟业务操作(比如存数据、改数据),不用改,照搬就行
            Thread.sleep(1000);
        } catch (Exception e) {
            // 出错了也不用管,这里只是防止程序崩溃
            e.printStackTrace();
        } finally {
            // 4. 释放锁(关机),必须写在这里,防止程序出错后锁没释放
            lock.unlock();
            System.out.println("程序(线程)退出methodA,释放锁了(相当于关机离开1号机)");
        }
    }
    // 方法B:也需要拿同一把锁(相当于重启电脑,再次占用1号机)
    public void methodB() {
        // 还是同一把锁(LOCK_NAME和上面一样,相当于还是1号机)
        RLock lock = redissonClient.getLock(LOCK_NAME);
        try {
            // 加锁(重启电脑),因为是同一个程序,直接重入,不用等
            lock.lock();
            System.out.println("程序(线程)进入methodB,重入锁成功(相当于重启电脑,继续用1号机)");
            // 模拟业务操作,不用改
            Thread.sleep(500);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 释放锁(重启完成),计数器减1,不是彻底释放
            lock.unlock();
            System.out.println("程序(线程)退出methodB,释放重入锁(相当于重启完成,继续使用1号机)");
        }
    }
}

第三步:测试

写一个简单的测试接口,调用上面的methodA,看控制台输出,就能知道可重入锁有没有生效,代码还是复制粘贴:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
// 测试接口,不用改
@RestController
public class TestLockController {
    // 注入上面写的LockDemo类
    @Autowired
    private LockDemo lockDemo;
    // 访问这个接口,就能测试可重入锁
    @GetMapping("/testLock")
    public String testLock() {
        // 调用methodA,会自动调用methodB,模拟重入场景
        lockDemo.methodA();
        return "测试成功!去看控制台输出";
    }
}

测试结果

启动项目,访问接口(http://localhost:8080/testLock),打开控制台,会看到下面的输出(顺序不能乱):

程序(线程)进入methodA,拿到锁了(相当于开机用1号机)
程序(线程)进入methodB,重入锁成功(相当于重启电脑,继续用1号机)
程序(线程)退出methodB,释放重入锁(相当于重启完成,继续使用1号机)
程序(线程)退出methodA,释放锁了(相当于关机离开1号机)​

解读一下这个结果:

程序先进入methodA、拿到锁(开机用1号机),然后直接进入methodB(重入成功,相当于重启电脑,不用重新刷卡),再依次释放methodB、methodA的锁(重启完成→关机离开)——这就证明可重入锁生效了,Redisson的计数器也在正常工作(拿2次、放2次,最后彻底释放)。

四、注意事项

  • 同一把锁的名字必须一致:相当于网吧里“1号机器”的锁,你开机用了1号机(拿锁),中途去厕所回来,还想坐1号机(重入锁),必须找的是“1号机”的锁,不能找“2号机”的锁——对应代码里,methodA和methodB用的LOCK_NAME,必须都是“myLock”,不能一个写“myLock”、一个写“myLock1”,否则不是同一把锁,没法重入。
  • lock()和unlock()要成对出现:相当于你去网吧,开机(lock()拿锁)、关机(unlock()释放锁)要对应。你开了一次机,就必须关一次机;开两次机(比如开机后又重启一次,相当于重入),就必须关两次机。多关一次(多unlock()),系统会报错;少关一次(少unlock()),这台机器会一直显示“已占用”,别人没法用(锁泄漏)。
  • unlock()必须写在finally里:相当于你在网吧上网,不管是正常关机(业务正常执行),还是突然停电、电脑死机(程序出错),机器都会自动释放(关机),不会一直占着位置。如果不写在finally里,程序一出错,就没人“关机”,这台机器永远被占用,别人用不了。

五、总结

可重入锁:你在网吧坐1号机(拿锁),中途去厕所、重启电脑(重入),回来还能直接坐1号机,不用等自己“释放”机器,不卡顿;

Redisson核心原理:靠“计数器”记拿锁次数,拿一次加1,放一次减1,减到0才彻底释放锁,避免提前释放和死锁;

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • redis分布式锁解决缓存双写一致性

    redis分布式锁解决缓存双写一致性

    这篇文章主要为大家介绍了redis分布式锁解决缓存双写一致性示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-05-05
  • Redis+Caffeine两级缓存的实现

    Redis+Caffeine两级缓存的实现

    本文主要介绍了Redis+Caffeine两级缓存的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-06-06
  • NestJS+Redis实现手写一个限流器

    NestJS+Redis实现手写一个限流器

    限流是大型系统必备的保护措施,本文将结合redis , lua 脚本 以及 Nestjs Guard 来实现 限流的效果,感兴趣的小伙伴可以跟随小编一起学习一下
    2023-11-11
  • SpringBoot读写Redis客户端并实现Jedis技术切换功能

    SpringBoot读写Redis客户端并实现Jedis技术切换功能

    这篇文章主要介绍了SpringBoot读写Redis客户端并实现技术切换功能,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2023-01-01
  • Redis使用bloom-filter过滤器实现推荐去重

    Redis使用bloom-filter过滤器实现推荐去重

    这篇文章主要介绍了Redis使用bloom-filter过滤器实现推荐去重,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-11-11
  • Redis存储经纬度信息的实现

    Redis存储经纬度信息的实现

    在一些向用户提供天气信息的业务场景中,我们通常会通过前端获取用户经纬度信息,传递给后端作为参数进行外部天气接口调用,本文就来详细的介绍一下Redis存储经纬度信息的实现,感兴趣的可以了解一下
    2025-10-10
  • Redis的持久化方案详解

    Redis的持久化方案详解

    在本篇文章里小编给大家整理的是关于Redis的持久化方案详解,有兴趣的朋友们可以参考下。
    2020-03-03
  • Python的Flask框架使用Redis做数据缓存的配置方法

    Python的Flask框架使用Redis做数据缓存的配置方法

    Redis数据库依赖于主存,在关系型数据库以外再配套Redis管理缓存数据将对性能会有很大的提升,这里我们就来看一下Python的Flask框架使用Redis做数据缓存的配置方法
    2016-06-06
  • Redis设置密码保护的实例讲解

    Redis设置密码保护的实例讲解

    今天小编就为大家分享一篇Redis设置密码保护的实例讲解,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2018-05-05
  • Redis缓存异常之缓存雪崩问题解读

    Redis缓存异常之缓存雪崩问题解读

    文章主要介绍了缓存雪崩、击穿和穿透问题,以及针对这些问题的解决方法,包括服务熔断、服务降级、请求限流和布隆过滤器等
    2025-01-01

最新评论