【Redis】分布式锁&Redisson

目录

1、为什么需要分布式锁

2、redis分布式锁原理

3,Jedis实现分布式锁

4,分布式锁的案例演进

4.1. 单机数据一致性

4.2 分布式数据一致性

5,redisson分布式锁

5.1 什么是 Redisson

5.2 添加Redisson依赖包

5.3 支持Redis多种连接模式

 5.3.1 单例模式

5.3.2 集群模式

5.3.3 哨兵模式

5.3.4 主从模式

5.4 Redisson操作Redis基本对象

5.4.1 字符串操作

5.4.2 对象操作

5.4.3 哈希操作

5.4.4 列表操作

5.4.5 Set集合操作

5.4.6 有序Set集合操作

5.4.7 BitSet集合

 5.4.8 Blocking Queue

5.4.9 布隆过滤器

5.4.10 分布式自增ID

5.5 Redisson分布式锁

5.5.1 可重入锁Reentrant Lock可重入锁

5.5.2 公平锁(Fair Lock)

5.5.3 联锁(MultiLock)

5.5.4  红锁(RedLock)

5.5.5 读写锁(ReadWriteLock)

5.5.6 信号量(Semaphore)

5.5.7 闭锁(CountDownLatch)

5.6 Redis命令和Redisson对象匹配列表

5.7 SpringBoot使用Redisson

5.7.1 实现分布式锁

5.7.2 可重入锁(Reentrant Lock)

5.7.3  公平锁(Fair Lock)

5.7.4  红锁(Red Lock)

5.7.5 读写锁(ReadWrite Lock)

5.8 Redisson和Jedis、Lettuce有什么区别?


1、为什么需要分布式锁

在单机环境,我们使用最多的是juc包里的单机锁,但是随着微服务分布式项目的普及,juc里的锁是不能控制分布锁环境的线程安全的,因为单机锁只能控制同个进程里的线程安全,不能控制多节点的线程安全,所以就需要使用分布式锁。

我们来假设一个最简单的秒杀场景:数据库里有一张表,column分别是商品ID,和商品ID对应的库存量,秒杀成功就将此商品库存量-1。现在假设有1000个线程来秒杀两件商品,500个线程秒杀第一个商品,500个线程秒杀第二个商品。我们来根据这个简单的业务场景来解释一下分布式锁。

通常具有秒杀场景的业务系统都比较复杂,承载的业务量非常巨大,并发量也很高。这样的系统往往采用分布式的架构来均衡负载。那么这1000个并发就会是从不同的地方过来,商品库存就是共享的资源,也是这1000个并发争抢的资源,这个时候我们需要将并发互斥管理起来。这就是分布式锁的应用。

2、redis分布式锁原理

学习之前先了解redis的命令,setnxexpire

setnx命令

SETNX是SET if not exists的简写,设置key的值,如果key值不存在,则可以设置,否则不可以设置,这个有点像juc中cas锁的原理

# setnx命令,相当于set和nx命令一起用
setnx tkey aaa
 EX : 设置指定的到期时间(以秒为单位)。

 PX : 设置指定的到期时间(以毫秒为单

 NX : 仅在键不存在时设置键。

 XX : 只有在键已存在时才设置。

expire命令

如果只使用setnx不加上过期时间,手动释放锁时候出现异常,就会导致一直解不了锁,所以还是要加上expire命令来设置过期时间。

  • 保证原子性

但是又有一个问题,设置过期时间时候报错了,也同样会导致锁释放不了,所以为了保证原子性,需要这两个命令一起执行

expire命令

如果只使用setnx不加上过期时间,手动释放锁时候出现异常,就会导致一直解不了锁,所以还是要加上expire命令来设置过期时间。

保证原子性
但是又有一个问题,设置过期时间时候报错了,也同样会导致锁释放不了,所以为了保证原子性,需要这两个命令一起执行

分布式锁实现原则 

  1. 互斥性,同一时刻,智能有一个客户端持有锁。
  2. 防止死锁发生,如果持有锁的客户端崩溃没有主动释放锁,也要保证锁可以正常释放及其他客户端可以正常加锁。
  3. 加锁和释放锁必须是同一个客户端。
  4. 容错性,只有redis还有节点存活,就可以进行正常的加锁解锁操作。

补充知识

Redis 2.6.12 之前的版本中采用 setnx + expire 方式实现分布式锁,在 Redis 2.6.12 版本后 setnx 增加了过期时间参数。

3,Jedis实现分布式锁

引入Jedis jar包,在pom.xml文件增加代码:

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>2.9.0</version>
</dependency>

 调用jedis的set()实现加锁,加锁代码如下:

/**
 * @description:
 * @author: 程序员大彬
 * @time: 2021-08-01 17:13
 */
public class RedisTest {

    private static final String LOCK_SUCCESS = "OK";
    private static final String SET_IF_NOT_EXIST = "NX";
    private static final String SET_EXPIRE_TIME = "PX";

    @Autowired
    private JedisPool jedisPool;

    public boolean tryGetDistributedLock(String lockKey, String requestId, int expireTime) {
        Jedis jedis = jedisPool.getResource();
        String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_EXPIRE_TIME, expireTime);

        if (LOCK_SUCCESS.equals(result)) {
            return true;
        }
        return false;
    }
}

各参数说明:

  • lockKey:使用key来当锁,需要保证key是唯一的。可以使用系统号拼接自定义的key。
  • requestId:表示这把锁是哪个请求加的,可以使用机器ip拼接当前线程名称。在解锁的时候需要判断当前请求是否持有锁,防止误解锁。比如客户端A加锁,在执行解锁之前,锁过期了,此时客户端B尝试加锁成功,然后客户端A再执行del()方法,则将客户端B的锁给解除了。
  • NX:意思是SET IF NOT EXIST,保证如果已有key存在,则不作操作,过段时间继续重试。NX参数保证只有一个客户端能持有锁。
  • PX:给key加一个过期的设置,具体时间由expireTime决定。
  • expireTime:设置key的过期时间,防止异常导致锁没有释放。

解锁

首先需要获取锁对应的value值,检查是否与requestId相等,如果相等则删除锁。这里使用lua脚本实现原子操作,保证线程安全。

使用eval命令执行Lua脚本的时候,不会有其他脚本或 Redis 命令被执行,实现组合命令的原子操作。lua脚本如下:

//KEYS[1]是lockKey,ARGV[1]是requestId
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));

Jedis的eval()方法源码如下:

public Object eval(String script, List<String> keys, List<String> args) {
    return this.eval(script, keys.size(), getParams(keys, args));
}

lua脚本的意思是:调用get获取锁(KEYS[1])对应的value值,检查是否与requestId(ARGV[1])相等,如果相等则调用del删除锁。否则返回0。

完整的解锁代码如下:

public class RedisTest {
    private static final Long RELEASE_SUCCESS = 1L;

    @Autowired
    private JedisPool jedisPool;

    public boolean releaseDistributedLock(String lockKey, String requestId) {
        Jedis jedis = jedisPool.getResource();
        ////KEYS[1]是lockKey,ARGV[1]是requestId
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));

        if (RELEASE_SUCCESS.equals(result)) {
            return true;
        }
        return false;
    }
}

完整的工具类如下:

package com.shuangyueliao.shuangcloud.redislock;

import redis.clients.jedis.Jedis;

import java.util.Collections;

public class RedisTool {

    private static final String LOCK_SUCCESS = "OK";
    private static final String SET_IF_NOT_EXIST = "NX";
    private static final String SET_WITH_EXPIRE_TIME = "PX";

    /**
     * 尝试获取分布式锁
     *
     * @param jedis      Redis客户端
     * @param lockKey    锁
     * @param requestId  请求标识
     * @param expireTime 超期时间
     * @return 是否获取成功
     */
    public static boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) {

        String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
        if (LOCK_SUCCESS.equals(result)) {
            return true;
        }
        return false;

    }

    private static final Long RELEASE_SUCCESS = 1L;

    /**
     * 释放分布式锁
     *
     * @param jedis     Redis客户端
     * @param lockKey   锁
     * @param requestId 请求标识
     * @return 是否释放成功
     */
    public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) {

        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));

        if (RELEASE_SUCCESS.equals(result)) {
            return true;
        }
        return false;
    }


}

4,分布式锁的案例演进

4.1. 单机数据一致性

单机数据一致性架构如下图所示:多个可客户访问同一个服务器,连接同一个数据库。

场景描述:

客户端模拟购买商品过程,在Redis中设定库存总数剩100个,多个客户端同时并发购买。

设置商品库存:

编写后端代码:

实际测试结果如下:

测试结果出现多个用户购买同一商品,发生了数据不一致问题!

解决办法:单体应用的情况下,对并发的操作进行加锁操作,保证对数据的操作具有原子性

1,synchronized

2,ReentrantLock

测试结果如下: 

4.2 分布式数据一致性

上面解决了单体应用的数据一致性问题,但如果是分布式架构部署呢,架构如下:

提供两个服务,端口分别为80018002,连接同一个Redis服务,在服务前面有一台Nginx作为负载均衡。

两台服务代码相同,只是端口不同

80018002两个服务启动,每个服务依然用ReentrantLock加锁,用Jmeter做并发测试,发现会出现数据一致性问题!

解决方式一:

取消单机锁,下面使用redisset命令来实现分布式加锁

SET KEY VALUE [EX seconds] [PX milliseconds] [NX|XX]

  • EX seconds 设置指定的到期时间(以秒为单位)
  • PX milliseconds 设置指定的到期时间(以毫秒为单位)
  • NX 仅在键不存在时设置键
  • XX 只有在键已存在时才设置

上面的代码,可以解决分布式架构中数据一致性问题。但再仔细想想,还是会有问题,下面进行改进。

方式二(改进方式一)

在上面的代码中,如果程序在运行期间,部署了微服务jar包的机器突然挂了,代码层面根本就没有走到finally代码块,也就是说在宕机前,锁并没有被删除掉,这样的话,就没办法保证解锁,所以,这里需要对这个key加一个过期时间,Redis中设置过期时间有两种方法:

  • template.expire(REDIS_LOCK,10, TimeUnit.SECONDS)
  • template.opsForValue().setIfAbsent(REDIS_LOCK, value,10L,TimeUnit.SECONDS)

第一种方法需要单独的一行代码,且并没有与加锁放在同一步操作,所以不具备原子性,也会出问题。

第二种方法在加锁的同时就进行了设置过期时间,所有没有问题,这里采用这种方式

调整下代码,在加锁的同时,设置过期时间:

这种方式解决了因服务突然宕机而无法释放锁的问题。但再仔细想想,还是会有问题,下面进行改进。

方式三(改进方式二)

方式二设置了key的过期时间,解决了key无法删除的问题,但问题又来了,上面设置了key的过期时间为10秒,如果业务逻辑比较复杂,需要调用其他微服务,处理时间需要15秒(模拟场景,别较真),而当10秒钟过去之后,这个key就过期了,其他请求就又可以设置这个key,此时如果耗时15秒的请求处理完了,回来继续执行程序,就会把别人设置的key给删除了,这是个很严重的问题!

所以,谁上的锁,谁才能删除

 这种方式解决了因服务处理时间太长而释放了别人锁的问题。这样就没问题了吗?

方式四(改进方式三)

在上面方式三下,规定了谁上的锁,谁才能删除,但finally快的判断和del删除操作不是原子操作,并发的时候也会出问题,并发嘛,就是要保证数据的一致性,保证数据的一致性,最好要保证对数据的操作具有原子性。在Redisset命令介绍中,最后推荐Lua脚本进行锁的删除。

因为获取lock是一个TCP/IP请求,删除delete又是一个网络请求,两者之间存在时间差,如果网络异常等原因导致,不能保障原子操作。

方式五(改进方式四)

在方式四下,规定了谁上的锁,谁才能删除,并且解决了删除操作没有原子性问题。但还没有考虑缓存续命,以及Redis集群部署下,异步复制造成的锁丢失:主节点没来得及把刚刚set进来这条数据给从节点,就挂了。所以直接上RedLockRedisson落地实现。

5,redisson分布式锁

5.1 什么是 Redisson

Redisson - 是一个高级的分布式协调Redis客服端,能帮助用户在分布式环境中轻松实现一些Java的对象,Redisson、Jedis、Lettuce 是三个不同的操作 Redis 的客户端。

Jedis、Lettuce 的 API 更侧重对 Reids 数据库的 CRUD(增删改查)。

Redisson API 侧重于分布式开发,redisson实现的分布式锁,底层是setnx、expire和lua脚本(保证原子性)。

相比于 Jedis、Lettuce 等基于 redis 命令封装的客户端,Redisson 提供的功能更加高端和抽象,逼格高!

底层原理:

Redisson底层Redis客户端和Lettuce一样基于Netty框架实现

// 在使用多个客户端的情况下可以共享同一个EventLoopGroup
EventLoopGroup group = new NioEventLoopGroup();

RedisClientConfig config = new RedisClientConfig();
config.setAddress("redis://localhost:6379") // 或者用rediss://使用加密连接
      .setPassword("myPassword")
      .setDatabase(0)
      .setClientName("myClient")
      .setGroup(group);

RedisClient client = RedisClient.create(config);
RedisConnection conn = client.connect();
// 或
RFuture<RedisConnection> connFuture = client.connectAsync();

conn.sync(StringCodec.INSTANCE, RedisCommands.SET, "test", 0);
// 或
conn.async(StringCodec.INSTANCE, RedisCommands.GET, "test");

conn.close()
// 或
conn.closeAsync()

client.shutdown();
// 或
client.shutdownAsync();

Redisson在底层采用了高性能异步非阻塞式Java客户端,它同时支持异步和同步两种通信模式。如果有哪些命令Redisson还没提供支持,也可以直接通过调用底层Redis客户端来实现。Redisson支持的命令在Redis命令和Redisson对象匹配列表里做了详细对比参照。

5.2 添加Redisson依赖包

<dependency>
  <groupId>org.redisson</groupId>
  <artifactId>redisson</artifactId>
  <version>3.15.0</version>
</dependency>

 单机环境下,简单样例如下!

public class RedissonMain {
 
    public static void main(String[] args) {
        Config config = new Config();
        config.useSingleServer()
                .setAddress("redis://127.0.0.1:6379")
                .setPassword("123456")
                .setDatabase(0);
        //获取客户端
        RedissonClient redissonClient = Redisson.create(config);
        //获取所有的key
        redissonClient.getKeys().getKeys().forEach(key -> System.out.println(key));
        //关闭客户端
        redissonClient.shutdown();
    }
}

5.3 支持Redis多种连接模式

 5.3.1 单例模式
    RedissonClient redisson = Redisson.create();
    Config config = new Config();
	config.useSingleServer().setAddress("myRedisServer:6379");
    RedissonClient redisson = Redisson.create(config);
5.3.2 集群模式
Config config = new Config();
config.useClusterServers()
    .setScanInterval(2000) // 集群状态扫描间隔时间,单位是毫秒
    //可以用"rediss://"来启用SSL连接
    .addNodeAddress("redis://127.0.0.1:7000", "redis://127.0.0.1:7001")
    .addNodeAddress("redis://127.0.0.1:7002");
RedissonClient redisson = Redisson.create(config);
5.3.3 哨兵模式
Config config = new Config();
config.useSentinelServers()
    .setMasterName("mymaster")
    .addSentinelAddress("redis://127.0.0.1:26389", "redis://127.0.0.1:26379")
    .addSentinelAddress("redis://127.0.0.1:26319");
RedissonClient redisson = Redisson.create(config);
5.3.4 主从模式
Config config = new Config();
config.useMasterSlaveServers()
	.setMasterAddress("redis://127.0.0.1:6379")
    .addSlaveAddress("redis://127.0.0.1:6389", "redis://127.0.0.1:6332", "redis://127.0.0.1:6419")
    .addSlaveAddress("redis://127.0.0.1:6399");
RedissonClient redisson = Redisson.create(config);

5.4 Redisson操作Redis基本对象

使用 Redisson 操作 Redis 中的字符串、哈希、列表、集合、有序集合,以及布隆过滤器和分布式锁等功能。

5.4.1 字符串操作

Redisson 支持通过RBucket对象来操作字符串数据结构,通过RBucket实例可以设置value或设置value和有效期,简单样例如下!

//字符串操作
RBucket<String> rBucket =  redissonClient.getBucket("strKey");
// 设置value和key的有效期
rBucket.set("张三", 30, TimeUnit.SECONDS);
// 通过key获取value
System.out.println(redissonClient.getBucket("strKey").get());
5.4.2 对象操作

Redisson 支持将对象作为value存入redis,被存储的对象事先必须要实现序列化接口Serializable,否则会报错,简单样例如下!

public class Student implements Serializable {
    private Long id;
    private String name;
    private Integer age;
   //set、get...
 
    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
//Student对象
Student student = new Student();
student.setId(1L);
student.setName("张三");
student.setAge(18);
 
//对象操作
RBucket<Student> rBucket =  redissonClient.getBucket("objKey");
// 设置value和key的有效期
rBucket.set(student, 30, TimeUnit.SECONDS);
// 通过key获取value
System.out.println(redissonClient.getBucket("objKey").get());
5.4.3 哈希操作

Redisson 支持通过RMap对象来操作哈希数据结构,简单样例如下!

//哈希操作
RMap<String, String> rMap = redissonClient.getMap("mapkey");
// 设置map中key-value
rMap.put("id", "123");
rMap.put("name", "赵四");
rMap.put("age", "50");
 
//设置过期时间
rMap.expire(30, TimeUnit.SECONDS);
// 通过key获取value
System.out.println(redissonClient.getMap("mapkey").get("name"));
 
5.4.4 列表操作

Redisson 支持通过RList对象来操作列表数据结构,简单样例如下!

//字符串操作
RList<Student> rList = redissonClient.getList("listkey");
 
Student student1 = new Student();
student1.setId(1L);
student1.setName("张三");
student1.setAge(18);
rList.add(student1);
 
Student student2 = new Student();
student2.setId(2L);
student2.setName("李四");
student2.setAge(19);
rList.add(student2);
 
//设置过期时间
rList.expire(30, TimeUnit.SECONDS);
// 通过key获取value
System.out.println(redissonClient.getList("listkey"));
 
5.4.5 Set集合操作

Redisson 支持通过RSet对象来操作集合数据结构,简单样例如下!

//字符串操作
RSet<Student> rSet = redissonClient.getSet("setkey");
 
Student student1 = new Student();
student1.setId(1L);
student1.setName("张三");
student1.setAge(18);
rSet.add(student1);
 
Student student2 = new Student();
student2.setId(2L);
student2.setName("李四");
student2.setAge(19);
rSet.add(student2);
 
//设置过期时间
rSet.expire(30, TimeUnit.SECONDS);
// 通过key获取value
System.out.println(redissonClient.getSet("setkey"));
5.4.6 有序Set集合操作

Redisson 支持通过RSortedSet对象来操作有序集合数据结构,在使用对象来存储之前,实体对象必须先实现Comparable接口,并重写比较逻辑,否则会报错,简单样例如下!

public class Student implements Serializable, Comparable<Student> {
    private Long id;
    private String name;
    private Integer age;
    //get、set.....
    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
    @Override
    public int compareTo(Student obj) {
        return this.getId().compareTo(obj.getId());
    }
}
//有序集合操作
RSortedSet<Student> sortSetkey = redissonClient.getSortedSet("sortSetkey");
 
Student student1 = new Student();
student1.setId(1L);
student1.setName("张三");
student1.setAge(18);
sortSetkey.add(student1);
 
Student student2 = new Student();
student2.setId(2L);
student2.setName("李四");
student2.setAge(19);
sortSetkey.add(student2);
 
// 通过key获取value
System.out.println(redissonClient.getSortedSet("sortSetkey"));
5.4.7 BitSet集合
    RBitSet set = redisson.getBitSet("simpleBitset");
    set.set(0, true);
    set.set(1812, false);
    set.clear(0);
    set.addAsync("e");
    set.xor("anotherBitset");
 5.4.8 Blocking Queue

​ 还支持Queue, Deque, Blocking Deque

RBlockingQueue<SomeObject> queue = redisson.getBlockingQueue("anyQueue");
queue.offer(new SomeObject());
SomeObject obj = queue.peek();
SomeObject someObj = queue.poll();
SomeObject ob = queue.poll(10, TimeUnit.MINUTES);
5.4.9 布隆过滤器

布隆过滤器(Bloom Filter)是 1970 年由布隆提出的。它实际上是一个很长的二进制向量和一系列随机映射函数。

布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都比一般的算法要好的多,缺点是有一定的误识别率和删除困难。

Redisson 支持通过RBloomFilter对象来操作布隆过滤器,简单样例如下!

RBloomFilter rBloomFilter = redissonClient.getBloomFilter("seqId");
// 初始化预期插入的数据量为10000和期望误差率为0.01
rBloomFilter.tryInit(10000, 0.01);
// 插入部分数据
rBloomFilter.add("100");
rBloomFilter.add("200");
rBloomFilter.add("300");
//设置过期时间
rBloomFilter.expire(30, TimeUnit.SECONDS);
// 判断是否存在
System.out.println(rBloomFilter.contains("300"));
System.out.println(rBloomFilter.contains("200"));
System.out.println(rBloomFilter.contains("999"));
5.4.10 分布式自增ID

ID 是数据的唯一标识,传统的做法是利用 UUID 和数据库的自增 ID。

但由于 UUID 是无序的,不能附带一些其他信息,因此实际作用有限。

随着业务的发展,数据量会越来越大,需要对数据进行分表,甚至分库。分表后每个表的数据会按自己的节奏来自增,这样会造成 ID 冲突,因此这时就需要一个单独的机制来负责生成唯一 ID,redis 原生支持生成全局唯一的 ID。

简单样例如下!

final String lockKey = "aaaa";
//通过redis的自增获取序号
RAtomicLong atomicLong = redissonClient.getAtomicLong(lockKey);
//设置过期时间
atomicLong.expire(30, TimeUnit.SECONDS);
// 获取值
System.out.println(atomicLong.incrementAndGet());

5.5 Redisson分布式锁

Redisson 最大的亮点,也是使用最多的功能,就是提供了强大的分布式锁实现,特点是:使用简单、安全!

Config config = new Config();
config.useSingleServer()
        .setAddress("redis://127.0.0.1:6379")
        .setPassword("123456")
        .setDatabase(0);
RedissonClient redissonClient = Redisson.create(config);
//获取锁对象实例
final String lockKey = "abc";
RLock rLock = redissonClient.getLock(lockKey);
 
try {
    //尝试5秒内获取锁,如果获取到了,最长60秒自动释放
    boolean res = rLock.tryLock(5L, 60L, TimeUnit.SECONDS);
    if (res) {
        //成功获得锁,在这里处理业务
        System.out.println("获取锁成功");
    }
} catch (Exception e) {
    System.out.println("获取锁失败,失败原因:" + e.getMessage());
} finally {
    //无论如何, 最后都要解锁
    rLock.unlock();
}
 
//关闭客户端
redissonClient.shutdown();
5.5.1 可重入锁Reentrant Lock可重入锁

基于Redis的Redisson分布式可重入锁RLock Java对象实现了java.util.concurrent.locks.Lock接口。同时还提供了异步(Async)、反射式(Reactive)和RxJava2标准的接口。

RLock lock = redisson.getLock("anyLock");
// 最常见的使用方法
lock.lock();

大家都知道,如果负责储存这个分布式锁的Redisson节点宕机以后,而且这个锁正好处于锁住的状态时,这个锁会出现锁死的状态。为了避免这种情况的发生,Redisson内部提供了一个监控锁的看门狗,它的作用是在Redisson实例被关闭前,不断的延长锁的有效期。默认情况下,看门狗的检查锁的超时时间是30秒钟,也可以通过修改Config.lockWatchdogTimeout来另行指定。
另外Redisson还通过加锁的方法提供了leaseTime的参数来指定加锁的时间。超过这个时间后锁便自动解开了。

// 加锁以后10秒钟自动解锁
// 无需调用unlock方法手动解锁
lock.lock(10, TimeUnit.SECONDS);

// 尝试加锁,最多等待100秒,上锁以后10秒自动解锁
boolean res = lock.tryLock(100, 10, TimeUnit.SECONDS);
if (res) {
   try {
     ...
   } finally {
       lock.unlock();
   }
}

Redisson同时还为分布式锁提供了异步执行的相关方法:

RLock lock = redisson.getLock("anyLock");
lock.lockAsync();
lock.lockAsync(10, TimeUnit.SECONDS);
Future<Boolean> res = lock.tryLockAsync(100, 10, TimeUnit.SECONDS);

RLock对象完全符合Java的Lock规范。也就是说只有拥有锁的进程才能解锁,其他进程解锁则会抛出IllegalMonitorStateException错误。但是如果遇到需要其他进程也能解锁的情况,请使用分布式信号量Semaphore 对象.

@ResponseBody
@GetMapping(value = "/hello")
public String hello() {

    //1、获取一把锁,只要锁的名字一样,就是同一把锁
    RLock myLock = redisson.getLock("my-lock");

    //2、加锁
    myLock.lock();      //阻塞式等待。默认加的锁都是30s

    //1)、锁的自动续期,如果业务超长,运行期间自动锁上新的30s。不用担心业务时间长,锁自动过期被删掉
    //2)、加锁的业务只要运行完成,就不会给当前锁续期,即使不手动解锁,锁默认会在30s内自动过期,不会产生死锁问题

    // myLock.lock(10,TimeUnit.SECONDS);   //10秒钟自动解锁,自动解锁时间一定要大于业务执行时间
    //问题:在锁时间到了以后,不会自动续期(不会启动看门狗机制)
    //1、如果我们传递了锁的超时时间,就发送给redis执行脚本,进行占锁,默认超时就是 我们指定的时间
    //2、如果我们未指定锁的超时时间,就使用 lockWatchdogTimeout = 30 * 1000 【看门狗默认时间】
    //只要占锁成功,就会启动一个定时任务【重新给锁设置过期时间,新的过期时间就是看门狗的默认时间】,每隔10秒都会自动的再次续期,续成30秒
    // internalLockLeaseTime 【看门狗时间】 / 3, 10s
    try {
        System.out.println("加锁成功,执行业务..." + Thread.currentThread().getId());
        try { TimeUnit.SECONDS.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); }
    } catch (Exception ex) {
        ex.printStackTrace();
    } finally {
        //3、解锁  假设解锁代码没有运行,Redisson会不会出现死锁
        System.out.println("释放锁..." + Thread.currentThread().getId());
        myLock.unlock();
    }

    return "hello";
}
5.5.2 公平锁(Fair Lock)

基于Redis的Redisson分布式可重入公平锁也是实现了java.util.concurrent.locks.Lock接口的一种RLock对象。同时还提供了异步(Async)、反射式(Reactive)和RxJava2标准的接口。它保证了当多个Redisson客户端线程同时请求加锁时,优先分配给先发出请求的线程。所有请求线程会在一个队列中排队,当某个线程出现宕机时,Redisson会等待5秒后继续下一个线程,也就是说如果前面有5个线程都处于等待状态,那么后面的线程会等待至少25秒。

RLock fairLock = redisson.getFairLock("anyLock");
// 最常见的使用方法
fairLock.lock();

大家都知道,如果负责储存这个分布式锁的Redis节点宕机以后,而且这个锁正好处于锁住的状态时,这个锁会出现锁死的状态。为了避免这种情况的发生,Redisson内部提供了一个监控锁的看门狗,它的作用是在Redisson实例被关闭前,不断的延长锁的有效期。默认情况下,看门狗的检查锁的超时时间是30秒钟,也可以通过修改Config.lockWatchdogTimeout来另行指定。

另外Redisson还通过加锁的方法提供了leaseTime的参数来指定加锁的时间。超过这个时间后锁便自动解开了。

// 10秒钟以后自动解锁
// 无需调用unlock方法手动解锁
fairLock.lock(10, TimeUnit.SECONDS);

// 尝试加锁,最多等待100秒,上锁以后10秒自动解锁
boolean res = fairLock.tryLock(100, 10, TimeUnit.SECONDS);
...
fairLock.unlock();

Redisson同时还为分布式可重入公平锁提供了异步执行的相关方法:

RLock fairLock = redisson.getFairLock("anyLock");
fairLock.lockAsync();
fairLock.lockAsync(10, TimeUnit.SECONDS);
Future<Boolean> res = fairLock.tryLockAsync(100, 10, TimeUnit.SECONDS);
5.5.3 联锁(MultiLock)

基于Redis的Redisson分布式联锁RedissonMultiLock对象可以将多个RLock对象关联为一个联锁,每个RLock对象实例可以来自于不同的Redisson实例。

RLock lock1 = redissonInstance1.getLock("lock1");
RLock lock2 = redissonInstance2.getLock("lock2");
RLock lock3 = redissonInstance3.getLock("lock3");

RedissonMultiLock lock = new RedissonMultiLock(lock1, lock2, lock3);
// 同时加锁:lock1 lock2 lock3
// 所有的锁都上锁成功才算成功。
lock.lock();
...
lock.unlock();

大家都知道,如果负责储存某些分布式锁的某些Redis节点宕机以后,而且这些锁正好处于锁住的状态时,这些锁会出现锁死的状态。为了避免这种情况的发生,Redisson内部提供了一个监控锁的看门狗,它的作用是在Redisson实例被关闭前,不断的延长锁的有效期。默认情况下,看门狗的检查锁的超时时间是30秒钟,也可以通过修改Config.lockWatchdogTimeout来另行指定。

另外Redisson还通过加锁的方法提供了leaseTime的参数来指定加锁的时间。超过这个时间后锁便自动解开了。

RedissonMultiLock lock = new RedissonMultiLock(lock1, lock2, lock3);
// 给lock1,lock2,lock3加锁,如果没有手动解开的话,10秒钟后将会自动解开
lock.lock(10, TimeUnit.SECONDS);

// 为加锁等待100秒时间,并在加锁成功10秒钟后自动解开
boolean res = lock.tryLock(100, 10, TimeUnit.SECONDS);
...
lock.unlock();
5.5.4  红锁(RedLock)

基于Redis的Redisson红锁RedissonRedLock对象实现了Redlock介绍的加锁算法。该对象也可以用来将多个RLock对象关联为一个红锁,每个RLock对象实例可以来自于不同的Redisson实例。 

  • 1.如果有多个 redis 集群的时候,当且仅当从大多数(N/2+1,比如有3个 redis 节点,那么至少有2个节点)的 Redis 节点都取到锁,并且获取锁使用的总耗时小于锁失效时间时,锁才算获取成功
  • 2.如果获取失败,客户端会在所有的 Redis 实例上进行解锁操作
  • 3.集群环境下,redis 服务器直接不存在任何复制或者其他隐含的分布式协调机制,否则会存在实效的可能

RedissonRedLock简单使用样例如下!

Config config1 = new Config();
config1.useSingleServer().setAddress("redis://192.168.3.111:6379").setPassword("a123456").setDatabase(0);
RedissonClient redissonClient1 = Redisson.create(config1);
 
Config config2 = new Config();
config2.useSingleServer().setAddress("redis://192.168.3.112:6379").setPassword("a123456").setDatabase(0);
RedissonClient redissonClient2 = Redisson.create(config2);
 
Config config3 = new Config();
config3.useSingleServer().setAddress("redis://192.168.3.113:6379").setPassword("a123456").setDatabase(0);
RedissonClient redissonClient3 = Redisson.create(config3);
 
//获取多个 RLock 对象
final String lockKey = "abc";
RLock lock1 = redissonClient1.getLock(lockKey);
RLock lock2 = redissonClient2.getLock(lockKey);
RLock lock3 = redissonClient3.getLock(lockKey);
 
//根据多个 RLock 对象构建 RedissonRedLock (最核心的差别就在这里)
RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3);
 
try {
    //尝试5秒内获取锁,如果获取到了,最长60秒自动释放
    boolean res = redLock.tryLock(5L, 60L, TimeUnit.SECONDS);
    if (res) {
        //成功获得锁,在这里处理业务
        System.out.println("获取锁成功");
 
    }
} catch (Exception e) {
    System.out.println("获取锁失败,失败原因:" + e.getMessage());
} finally {
    //无论如何, 最后都要解锁
    redLock.unlock();
}

大家都知道,如果负责储存某些分布式锁的某些Redis节点宕机以后,而且这些锁正好处于锁住的状态时,这些锁会出现锁死的状态。为了避免这种情况的发生,Redisson内部提供了一个监控锁的看门狗,它的作用是在Redisson实例被关闭前,不断的延长锁的有效期。默认情况下,看门狗的检查锁的超时时间是30秒钟,也可以通过修改Config.lockWatchdogTimeout来另行指定。

另外Redisson还通过加锁的方法提供了leaseTime的参数来指定加锁的时间。超过这个时间后锁便自动解开了。

RedissonRedLock lock = new RedissonRedLock(lock1, lock2, lock3);
// 给lock1,lock2,lock3加锁,如果没有手动解开的话,10秒钟后将会自动解开
lock.lock(10, TimeUnit.SECONDS);

// 为加锁等待100秒时间,并在加锁成功10秒钟后自动解开
boolean res = lock.tryLock(100, 10, TimeUnit.SECONDS);
...
lock.unlock();
5.5.5 读写锁(ReadWriteLock)

基于Redis的Redisson分布式可重入读写锁RReadWriteLock Java对象实现了java.util.concurrent.locks.ReadWriteLock接口。其中读锁和写锁都继承了RLock接口。

分布式可重入读写锁允许同时有多个读锁和一个写锁处于加锁状态。

RReadWriteLock rwlock = redisson.getReadWriteLock("anyRWLock");
// 最常见的使用方法
rwlock.readLock().lock();
// 或
rwlock.writeLock().lock();

大家都知道,如果负责储存这个分布式锁的Redis节点宕机以后,而且这个锁正好处于锁住的状态时,这个锁会出现锁死的状态。为了避免这种情况的发生,Redisson内部提供了一个监控锁的看门狗,它的作用是在Redisson实例被关闭前,不断的延长锁的有效期。默认情况下,看门狗的检查锁的超时时间是30秒钟,也可以通过修改Config.lockWatchdogTimeout来另行指定。

另外Redisson还通过加锁的方法提供了leaseTime的参数来指定加锁的时间。超过这个时间后锁便自动解开了。

// 10秒钟以后自动解锁
// 无需调用unlock方法手动解锁
rwlock.readLock().lock(10, TimeUnit.SECONDS);
// 或
rwlock.writeLock().lock(10, TimeUnit.SECONDS);

// 尝试加锁,最多等待100秒,上锁以后10秒自动解锁
boolean res = rwlock.readLock().tryLock(100, 10, TimeUnit.SECONDS);
// 或
boolean res = rwlock.writeLock().tryLock(100, 10, TimeUnit.SECONDS);
...
lock.unlock();

使用范例

/**
* 保证一定能读到最新数据,修改期间,写锁是一个排它锁(互斥锁、独享锁),读锁是一个共享锁
* 写锁没释放读锁必须等待
* 读 + 读 :相当于无锁,并发读,只会在Redis中记录好,所有当前的读锁。他们都会同时加锁成功
* 写 + 读 :必须等待写锁释放
* 写 + 写 :阻塞方式
* 读 + 写 :有读锁。写也需要等待
* 只要有读或者写的存都必须等待
* @return
*/
@GetMapping(value = "/write")
@ResponseBody
public String writeValue() {
    String s = "";
    RReadWriteLock readWriteLock = redisson.getReadWriteLock("rw-lock");
    RLock rLock = readWriteLock.writeLock();
    try {
        //1、改数据加写锁,读数据加读锁
        rLock.lock();
        s = UUID.randomUUID().toString();
        ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();
        ops.set("writeValue",s);
        TimeUnit.SECONDS.sleep(10);
    } catch (InterruptedException e) {
        e.printStackTrace();
    } finally {
        rLock.unlock();
    }


    return s;
}

@GetMapping(value = "/read")
@ResponseBody
public String readValue() {
    String s = "";
    RReadWriteLock readWriteLock = redisson.getReadWriteLock("rw-lock");
    //加读锁
    RLock rLock = readWriteLock.readLock();
    try {
        rLock.lock();
        ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();
        s = ops.get("writeValue");
        try { TimeUnit.SECONDS.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); }
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        rLock.unlock();
    }

    return s;
}
5.5.6 信号量(Semaphore)

基于Redis的Redisson的分布式信号量(Semaphore)Java对象RSemaphore采用了与java.util.concurrent.Semaphore相似的接口和用法。同时还提供了异步(Async)、反射式(Reactive)和RxJava2标准的接口。

RSemaphore semaphore = redisson.getSemaphore("semaphore");
semaphore.acquire();
//或
semaphore.acquireAsync();
semaphore.acquire(23);
semaphore.tryAcquire();
//或
semaphore.tryAcquireAsync();
semaphore.tryAcquire(23, TimeUnit.SECONDS);
//或
semaphore.tryAcquireAsync(23, TimeUnit.SECONDS);
semaphore.release(10);
semaphore.release();
//或
semaphore.releaseAsync();

使用范例

/**
* 车库停车
* 3车位
* 信号量也可以做分布式限流
*/
@GetMapping(value = "/park")
@ResponseBody
public String park() throws InterruptedException {


    RSemaphore park = redisson.getSemaphore("park");
    park.acquire();     //获取一个信号、获取一个值,占一个车位
    boolean flag = park.tryAcquire();

    if (flag) {
        //执行业务
    } else {
        return "error";
    }

    return "ok=>" + flag;
}

@GetMapping(value = "/go")
@ResponseBody
public String go() {
    RSemaphore park = redisson.getSemaphore("park");
    park.release();     //释放一个车位
    return "ok";
}
5.5.7 闭锁(CountDownLatch)

基于Redisson的Redisson分布式闭锁(CountDownLatch)Java对象RCountDownLatch采用了与java.util.concurrent.CountDownLatch相似的接口和用法。

RCountDownLatch latch = redisson.getCountDownLatch("anyCountDownLatch");
latch.trySetCount(1);
latch.await();

// 在其他线程或其他JVM里
RCountDownLatch latch = redisson.getCountDownLatch("anyCountDownLatch");
latch.countDown();

使用范例

/**
* 放假、锁门
* 1班没人了
* 5个班,全部走完,我们才可以锁大门
* 分布式闭锁
*/

@GetMapping(value = "/lockDoor")
@ResponseBody
public String lockDoor() throws InterruptedException {


    RCountDownLatch door = redisson.getCountDownLatch("door");
    door.trySetCount(5);
    door.await();       //等待闭锁完成
    return "放假了...";
}

@GetMapping(value = "/gogogo/{id}")
@ResponseBody
public String gogogo(@PathVariable("id") Long id) {
    RCountDownLatch door = redisson.getCountDownLatch("door");
    door.countDown();       //计数-1
    return id + "班的人都走了...";
}

5.6 Redis命令和Redisson对象匹配列表

5.7 SpringBoot使用Redisson

 在Spring Boot中,可以通过Redisson的Spring支持来创建RedissonClient对象。

@Configuration
public class RedissonConfig {
    @Bean
    public RedissonClient redissonClient() {
        Config config = new Config();
        config.useSingleServer().setAddress("redis://localhost:6379");
        return Redisson.create(config);
    }
}

在上述代码中,创建了一个RedissonClient对象,并配置了连接Redis的地址。

5.7.1 实现分布式锁

使用RedissonClient对象获取RLock对象,RLock是Redisson提供的分布式锁接口。
通过RLock对象的lock方法来获取锁,并在获取锁成功后执行业务逻辑。
通过RLock对象的unlock方法来释放锁。

@Service
public class DistributedLockService {
    @Autowired
    private RedissonClient redissonClient;
    
    public void executeWithLock() {
        RLock lock = redissonClient.getLock("my_lock");
        try {
            lock.lock();
            // 执行业务逻辑...博客原文:
        } finally {
            lock.unlock();
        }
    }
}

在上述代码中,executeWithLock方法通过redissonClient获取了一个名为"my_lock"的锁,并通过lock方法获取锁。在获取锁成功后,可以执行业务逻辑。最后,通过unlock方法释放锁。

Redisson还提供了其他一些功能强大的分布式锁实现方式,如可重入锁、公平锁、红锁、读写锁等。这些锁的实现方式更加灵活和强大,可以根据实际需求进行选择和使用。

使用Redisson实现分布式锁时,需要确保Redis服务器的可用性和稳定性,以避免单点故障导致的锁失效或锁的不稳定情况。此外,还需要根据具体的应用场景和需求,合理设置锁的过期时间,避免锁的长时间占用。

5.7.2 可重入锁(Reentrant Lock)

可重入锁是指同一个线程可以多次获得同一个锁,而不会发生死锁。Redisson的可重入锁实现是基于Redis的分布式锁的一种特例。

@Service
public class ReentrantLockService {
    @Autowired
    private RedissonClient redissonClient;
    
    public void executeWithReentrantLock() {
        RLock lock = redissonClient.getLock("my_lock");
        try {
            lock.lock();
            // 执行业务逻辑...小小鱼儿小小林的博客测试
            executeWithReentrantLock();
        } finally {
            lock.unlock();
        }
    }
}
5.7.3  公平锁(Fair Lock)

公平锁是指按照线程请求锁的顺序来分配锁。Redisson的公平锁实现可以保证多个线程按照先后顺序获取锁。

@Service
public class FairLockService {
    @Autowired
    private RedissonClient redissonClient;
    
    public void executeWithFairLock() {
        RLock lock = redissonClient.getFairLock("my_lock");
        try {
            lock.lock();
            // 执行业务逻辑...小小鱼儿小小林的博客测试
        } finally {
            lock.unlock();
        }
    }
}

 在上述代码中,使用redissonClient获取了一个名为"my_lock"的公平锁,并通过lock方法获取锁。在获取锁成功后,可以执行业务逻辑。最后,通过unlock方法释放锁。

5.7.4  红锁(Red Lock)

红锁是指在多个Redis节点上获取锁,以提高分布式系统的可靠性和容错性。Redisson的红锁实现是基于Redis的分布式锁的一种优化方式。

@Service
public class RedLockService {
    @Autowired
    private RedissonClient redissonClient;
    
    public void executeWithRedLock() {
        RLock lock1 = redissonClient.getLock("lock1");
        RLock lock2 = redissonClient.getLock("lock2");
        RLock lock3 = redissonClient.getLock("lock3");
        
        RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3);
        try {
            redLock.lock();
            // 执行业务逻辑...
        } finally {
            redLock.unlock();
        }
    }
}

在上述代码中,使用redissonClient分别获取了名为"lock1"、"lock2"和"lock3"的锁,并通过RedissonRedLock将这些锁组合成红锁。在获取红锁成功后,可以执行业务逻辑。最后,通过unlock方法释放红锁。

5.7.5 读写锁(ReadWrite Lock)

读写锁是指在多线程环境下,对于读操作可以并行进行,对于写操作必须互斥进行。Redisson的读写锁实现提供了读锁和写锁两种操作。

@Service
public class ReadWriteLockService {
    @Autowired
    private RedissonClient redissonClient;
    
    public void readWithReadWriteLock() {
        RReadWriteLock rwLock = redissonClient.getReadWriteLock("my_lock");
        RLock readLock = rwLock.readLock();
        try {
            readLock.lock();
            // 执行读操作...
        } finally {
            readLock.unlock();
        }
    }
    
    public void writeWithReadWriteLock() {
        RReadWriteLock rwLock = redissonClient.getReadWriteLock("my_lock");
        RLock writeLock = rwLock.writeLock();
        try {
            writeLock.lock();
            // 执行写操作...
        } finally {
            writeLock.unlock();
        }
    }
}

在上述代码中,使用redissonClient获取了一个名为"my_lock"的读写锁,并通过readLock方法获取读锁,通过writeLock方法获取写锁。在获取锁成功后,可以执行相应的读操作或写操作。最后,通过unlock方法释放锁。

5.8 Redisson和Jedis、Lettuce有什么区别?

Redisson和Jedis、Lettuce有什么区别?倒也不是雷锋和雷锋塔

Redisson和它俩的区别就像一个用鼠标操作图形化界面,一个用命令行操作文件。Redisson是更高层的抽象,Jedis和Lettuce是Redis命令的封装。

Jedis是Redis官方推出的用于通过Java连接Redis客户端的一个工具包,提供了Redis的各种命令支持

Lettuce是一种可扩展的线程安全的 Redis 客户端,通讯框架基于Netty,支持高级的 Redis 特性,比如哨兵,集群,管道,自动重新连接和Redis数据模型。Spring Boot 2.x 开始 Lettuce 已取代 Jedis 成为首选 Redis 的客户端。

Redisson是架设在Redis基础上,通讯基于Netty的综合的、新型的中间件,企业级开发中使用Redis的最佳范本

Jedis把Redis命令封装好,Lettuce则进一步有了更丰富的Api,也支持集群等模式。但是两者也都点到为止,只给了你操作Redis数据库的脚手架,而Redisson则是基于Redis、Lua和Netty建立起了成熟的分布式解决方案,甚至redis官方都推荐的一种工具集。

### Redis 分布式锁Redisson 的实现原理 #### 1. **Redis 分布式锁** Redis 分布式锁的核心思想是通过 `SETNX` 和 `EXPIRE` 来实现加锁操作。在早期版本(Redis 2.6.12之前),由于 `SETNX` 不支持设置过期时间,因此需要分两步完成:先调用 `SETNX` 创建键值对表示锁定状态,再调用 `EXPIRE` 设置过期时间以防止死锁[^3]。 然而这种两步操作无法保证原子性,可能会因网络延迟或其他异常导致锁未成功创建却设置了过期时间。为此,Redis 2.6.12引入了新的命令 `SET key value NX PX milliseconds`,其中 `NX` 表示只有当键不存在时才执行设置操作,`PX` 则用于指定毫秒级的过期时间。这种方式能够在一个命令内完成加锁并设定超时,从而有效解决了上述问题。 #### 2. **Redisson 实现分布式锁** Redisson 是基于 Redis 开发的一个 Java 客户端库,它不仅实现了更高级别的抽象接口,还提供了多种类型的分布式对象和服务功能。对于分布式锁而言,Redisson 提供了一种更加健壮可靠的解决方案——`RLock` 接口及其子类实例化方式。 - **加锁逻辑** Redisson 使用 Lua 脚本来确保整个加锁过程具有原子性。该脚本会检查目标资源是否已被占用;如果未被占用,则尝试获取锁并将当前线程 ID 记录下来作为持有者的唯一标识符[^1]。 - **续命机制** 当某个客户端成功获得锁之后,Redisson 会在后台启动一个定时器任务定期向服务器发送续约请求延长锁的有效期限,直到显式解锁为止。此设计可以避免因长时间运行的任务而导致锁提前失效的情况发生[^2]。 - **自旋重试策略** 如果初次未能取得所需资源,则按照预定义间隔不断重复尝试直至达到最大等待时限或最终放弃争夺控制权。 - **公平性和可靠性保障措施** 在某些特殊情况下(比如网络分区), 可能会出现部分节点认为自己已经拿到了全局唯一的锁,但实际上其他地方也有竞争者存在的情形下, redisson 还特别考虑到了这一点并通过内部复杂的协调算法尽可能减少冲突概率[^4]. #### 性能对比分析 | 特性 | Redis 原生分布锁 | Redisson | |-------------------------|------------------------------------------|----------------------------------| | 加锁效率 | 较高 | 略低 | | 锁安全性 | 存在网络抖动等问题 | 更安全可靠 | | 功能扩展能力 | 单纯提供基础加解鎖功能 | 支持更多特性如自动续租、可重入等 | | 易用程度 | 需要开发者手动处理很多细节 | API 封装良好易于集成 | 从表中可以看出虽然原生态方法简单高效但在实际应用过程中往往面临诸多挑战;而借助第三方工具包则可以在一定程度上弥补这些不足之处. ```java // 示例代码展示如何利用Redisson进行分布式锁管理 import org.redisson.api.RLock; import org.redisson.api.RedissonClient; public class DistributedLockExample { private final RedissonClient redissonClient; public void acquireAndReleaseLock(String lockName) throws InterruptedException{ RLock lock = redissonClient.getLock(lockName); try { boolean isLocked = lock.tryLock(10, TimeUnit.SECONDS); // 尝试获取锁最长等待时间为10秒 if(isLocked){ System.out.println(Thread.currentThread().getName()+" acquired the lock."); Thread.sleep(5000L); // Simulate some work }else{ System.err.println("Failed to get lock after waiting..."); } } finally { if(lock.isHeldByCurrentThread()){ lock.unlock(); System.out.println(Thread.currentThread().getName()+ " released the lock."); } } } } ``` ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

常生果

喜欢我,请支持我

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值