Redisson学习教程(B站诸葛)

在这里插入图片描述


  • 弱智级别
package org.example.controller;public class IndexController {@Autowiredprivate Redisson redisson;@Autowiredprivate StringRedisTemplate stringRedisTemplate;@RequestMapping("/deduct_storck")public String deductStock() {String lockKey = "product:1001";//再redis里排队的只有第一个请求能执行成功Boolean result = stringRedisTemplate.opsForValue().setIfAbsent("lockKey", "zhuge");//相当于jedis.setnx(k,v)  如果这个值不存在,我就设置这个key//result代表执行成功,原来没有这个key,如果这个值是false 代表这个key存在if (!result) {return "error";}if (stock > 0) {int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));int realStock = stock - 1;stringRedisTemplate.opsForValue().set("stock", realStock + "");System.out.println("扣减成功 ");} else {System.out.println("扣减失败,库存不足 ");}stringRedisTemplate.delete("lockKey");}
}

在这里插入图片描述
如果扣减库存的失效,那么就出问题了,下面就无法执行,锁无法释放,导致报错,这个商品id就一直被锁着 了

  stringRedisTemplate.delete("lockKey");

因为服务器扣减库存抛异常,改进一版

package org.example.controller;public class IndexController {@Autowiredprivate Redisson redisson;@Autowiredprivate StringRedisTemplate stringRedisTemplate;@RequestMapping("/deduct_storck")public String deductStock() {String lockKey = "product:1001";//再redis里排队的只有第一个请求能执行成功Boolean result = stringRedisTemplate.opsForValue().setIfAbsent("lockKey", "zhuge");//相当于jedis.setnx(k,v)  如果这个值不存在,我就设置这个key//result代表执行成功,原来没有这个key,如果这个值是false 代表这个key存在if (!result) {return "error";}try{     int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));if (stock > 0) {int realStock = stock - 1;stringRedisTemplate.opsForValue().set("stock", realStock + "");System.out.println("扣减成功 ");} else {System.out.println("扣减失败,库存不足 ");}}finally {stringRedisTemplate.delete("lockKey");}}
}

在关键的代码块加了

try{} finally {}

  • 改进二版:
  • 如果这个服务器宕机了,在扣减库存的时候,这个时候你应该怎么办?????finally 无法执行,是不是又导致死锁了。
        Boolean result = stringRedisTemplate.opsForValue().setIfAbsent("lockKey", "zhuge");stringRedisTemplate.expire(lockKey, 10, TimeUnit.SECONDS);if (!result) {return "error";}

解决方案: 加过期的时间


第三版:如果枷锁之后,系统宕机, 还是导致死锁,所以要保证原子性:

//        Boolean result = stringRedisTemplate.opsForValue().setIfAbsent("lockKey", "zhuge");
//        stringRedisTemplate.expire(lockKey, 10, TimeUnit.SECONDS);Boolean result = stringRedisTemplate.opsForValue().setIfAbsent("lockKey", "zhuge", 10, TimeUnit.SECONDS);

原子性:保证了同时执行,一行代码,注意方法的参数;

  • 高并发下出现的问题:假如执行这个请求执行完成1500ms , 过期时间1000ms, 也就是说第一个请求还没执行完成,就已经过期了,然而此时第二个请求过来了,加入执行时间800ms 扣减库存占用500ms, 第二个请求本来是加锁了,但是第一个因为提前过期而没有手动释放锁,导致你现在释放了第二个请求过来的锁。这样加的锁就无效了。然后导致超卖问题,就是你库存100卖了1000的货。

问题的根本原因:我加的锁被别人删除了, 怎么解决???

package org.example.controller;import java.util.UUID;
import java.util.concurrent.TimeUnit;public class IndexController {@Autowiredprivate Redisson redisson;@Autowiredprivate StringRedisTemplate stringRedisTemplate;@RequestMapping("/deduct_storck")public String deductStock() {String lockKey = "product:1001";String clientId = UUID.randomUUID().toString();Boolean result = stringRedisTemplate.opsForValue().setIfAbsent("lockKey", clientId, 10, TimeUnit.SECONDS);if (!result) {return "error";}try {int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));if (stock > 0) {int realStock = stock - 1;stringRedisTemplate.opsForValue().set("stock", realStock + "");System.out.println("扣减成功 ");} else {System.out.println("扣减失败,库存不足 ");}} finally {if (clientId.equals(stringRedisTemplate.opsForValue().get("lockKey"))) {stringRedisTemplate.delete("lockKey");}}}
}

在这里插入图片描述
他在加锁哪里,对锁进行了一个id的匹配


关于锁续命:
天空一声雷响,主角登场,文章属于纸上谈兵,目前没有实战

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

1、单机模式useSingleServer,可以变

@Configuration
public class RedissonConfig {@Beanpublic RedissonClient redissonClient(){// 配置Config config = new Config();config.useSingleServer().setAddress("redis://192.168.150.101:6379").setPassword("123321");// 创建RedissonClient对象return Redisson.create(config);}
}

也可以

package org.example;import jdk.nashorn.internal.runtime.regexp.joni.Config;@SpringBootApplication
public class Main {public static void main(String[] args) {SpringApplication.run(Main.class, args);System.out.println("程序启动");}@Beanpublic RedissonClient redissonClient() {// 配置Config config = new Config();config.useSingleServer().setAddress("redis://192.168.150.101:6379").setPassword("123321");// 创建RedissonClient对象return Redisson.create(config);}
}

改进版

    public String deductStock() {
//        String lockKey = "product:1001";
//        String clientId = UUID.randomUUID().toString();
//        Boolean result = stringRedisTemplate.opsForValue().setIfAbsent("lockKey", clientId, 10, TimeUnit.SECONDS);
//
//        if (!result) {
//            return "error";
//        }RLock redissonLock = redisson.getLock("lockKey");redissonLock.lock();try {int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));if (stock > 0) {int realStock = stock - 1;stringRedisTemplate.opsForValue().set("stock", realStock + "");System.out.println("扣减成功 ");} else {System.out.println("扣减失败,库存不足 ");}} finally {redissonLock.unlock();
/*            if (clientId.equals(stringRedisTemplate.opsForValue().get("lockKey"))) {stringRedisTemplate.delete("lockKey");}*/}}

视频最后的效果

package com.example.demo.controller;import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.RequestMapping;public class IndexController {@Autowiredprivate Redisson redisson;@Autowiredprivate StringRedisTemplate stringRedisTemplate;@RequestMapping("/deduct_storck")public String deductStock() {RLock redissonLock = redisson.getLock("lockKey");redissonLock.lock();try {int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));if (stock > 0) {int realStock = stock - 1;stringRedisTemplate.opsForValue().set("stock", realStock + "");System.out.println("扣减成功 ");} else {System.out.println("扣减失败,库存不足 ");}} finally {redissonLock.unlock();}return "deductStock";}
}
        <dependency><groupId>org.redisson</groupId><artifactId>redisson</artifactId><version>3.31.0</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency>

分别对应依赖

    @Autowiredprivate Redisson redisson;@Autowiredprivate StringRedisTemplate stringRedisTemplate;
  • 为什么要用分布式锁,用一般的锁不行吗,分布式锁的应用场景是什么?使用 分布式锁 的主要原因是要在多个不同的 机器进程 之间进行资源的共享和同步。在分布式系统中,应用可能在多个节点上运行,而普通的 本地锁(如 Java 中的 synchronizedReentrantLock)只能在同一个 JVM(Java Virtual Machine)进程内有效,无法跨进程、跨机器共享和同步。

1. 为什么普通锁不行

普通锁(如 synchronizedReentrantLock)是 局部锁,只能在同一个 进程线程 内部工作。当应用部署在多个机器或容器上时,普通锁就无法保证跨节点的同步与协调。比如:

  • 如果多个服务实例或容器在不同的机器上运行,它们之间无法共享本地的锁。
  • 如果不同服务实例同时访问某个共享资源(如数据库、缓存、文件等),就可能导致资源竞争,产生 并发冲突数据不一致

这时就需要 分布式锁 来保证多个分布式系统节点在操作共享资源时,能够遵循某种约定进行同步,避免数据竞争。

2. 分布式锁的应用场景

分布式锁主要用于以下几个典型的场景:

1. 多个服务实例竞争共享资源

在微服务架构或分布式系统中,通常会有多个服务实例运行在不同的机器上。当这些服务需要对一个共享资源(如数据库中的某个数据、缓存、文件等)进行操作时,需要避免同时进行修改,防止数据不一致或者产生竞态条件。这时分布式锁就可以确保在同一时刻只有一个服务实例在访问这个共享资源。

场景示例

  • 订单系统:多个微服务实例可能同时收到用户支付请求,如果没有分布式锁,可能会导致重复创建订单或者扣款。
  • 限流:多个服务实例需要共同控制某个资源的访问量,比如访问次数、流量限制等,可以通过分布式锁确保同一时刻只有一个服务实例在执行限流操作。
2. 防止重复任务的执行

在分布式系统中,如果没有协调机制,可能会出现多个节点同时执行同一个任务的情况。通过分布式锁,可以确保在同一时刻只有一个节点能够执行某个任务,避免任务重复执行。

场景示例

  • 定时任务:比如数据清理任务、缓存刷新任务等,如果有多个节点同时执行相同的定时任务,可能会导致资源浪费或者数据重复处理。使用分布式锁可以确保任务只在一个节点上执行。
  • 唯一性任务:比如生成唯一的 ID 或令牌时,通过分布式锁可以保证 ID 生成过程的唯一性,防止多个节点生成相同的 ID。
3. 协调跨节点的操作

在一些需要跨节点协同的场景中,多个节点可能需要顺序地进行某个操作。分布式锁可以确保这些操作按照特定的顺序执行,而不会发生并发冲突。

场景示例

  • 分布式事务:在分布式数据库或多服务之间的事务处理中,如果不同的节点需要修改同一份资源,分布式锁可以帮助确保事务的一致性和顺序性。
  • 唯一任务处理:比如某个任务只应该被一个节点处理,其他节点需要等待。
4. 资源的有限访问控制

在某些情况下,某些资源(如 API 请求、数据库连接池等)是有限的,多个节点可能会争抢访问这些资源。使用分布式锁可以控制资源的访问顺序,确保资源不会被过度占用。

场景示例

  • 限制高频访问:如果你有一个高频率的 API 或数据库接口,分布式锁可以确保不会有多个服务实例同时发送请求。
  • 限制并发请求:例如,某些操作需要保证最大并发数,分布式锁可以用于控制并发请求的数量,确保系统不会过载。

3. 分布式锁的实现方式

分布式锁的实现方式有多种,常见的包括:

  • Redis:通过 Redis 提供的 SETNX 命令或者 Redis 的 RedLock 算法实现分布式锁。
  • ZooKeeper:利用 ZooKeeper 的临时节点来实现分布式锁,ZooKeeper 本身就是为了协调分布式系统而设计的。
  • Etcd:类似于 ZooKeeper,Etcd 也可以用来实现分布式锁,通常用于 Kubernetes 等分布式系统中。
  • 数据库:通过数据库的行锁或其他机制实现,但效率相对较低,适合不频繁需要获取锁的场景。

4. 分布式锁的挑战

分布式锁虽然可以解决跨节点并发问题,但在实现和使用时也有一些挑战:

  • 死锁:分布式系统中的锁可能因网络延迟或节点故障等原因导致死锁,需要特别小心管理锁的获取和释放。
  • 性能问题:频繁获取和释放分布式锁会增加系统的延迟,可能成为瓶颈。
  • 过期时间管理:如果分布式锁没有合理的过期时间控制,可能会导致锁无法及时释放,影响系统的可用性。
  • 容错性:分布式锁需要具备容错机制,防止由于单点故障或网络问题导致锁失效。

总结

  • 分布式锁 是解决跨进程、跨机器资源同步与竞争的一个重要工具,适用于分布式系统、微服务架构中需要对共享资源进行安全访问的场景。
  • 普通的 本地锁 只能在同一进程内使用,无法满足跨机器同步的需求,而分布式锁则能有效地协调多个节点之间的资源访问。
  • 常见的应用场景包括 共享资源竞争、任务去重、跨节点协同工作、资源访问控制等,但也需要注意合理管理锁的获取和释放,避免性能问题和死锁。

是不是最好用分布式锁,尽量避免使用Synchronized,Reentrantlock?????????????
并不是所有场景下都需要使用 分布式锁,也不能说一定要避免使用 synchronizedReentrantLock。实际上,这两者(synchronizedReentrantLock)和 分布式锁 各自有其适用的场景,使用时要根据具体的需求和系统架构来选择。

1. synchronizedReentrantLock 的适用场景

1.1 本地锁的优势
  • 简单高效synchronizedReentrantLock 是 Java 内建的本地锁机制,它们的实现非常简单,开销低,并且效率高。它们适用于 单机应用,即只有一个 JVM 实例运行的情况。这种情况下,不需要复杂的分布式协调,使用本地锁完全可以满足并发控制的需求。

  • 易于使用

    • synchronized 是 Java 的语言级别的关键字,使用起来简洁直接,不需要额外的依赖。
    • ReentrantLock 提供了比 synchronized 更加灵活的功能,比如手动加锁、解锁、可重入、可中断锁等特性。
1.2 使用场景:
  • 多线程环境下的同步:当多个线程需要对同一个对象的资源进行并发操作时(如多个线程修改同一内存中的数据结构),synchronizedReentrantLock 是常用的本地锁,可以确保数据的一致性和完整性。
  • 在单台机器上运行:如果你的应用只部署在单台服务器上,且所有线程都运行在同一 JVM 进程内,那么本地锁是最合适的选择。

2. 分布式锁的必要性

分布式锁解决的主要问题是 跨进程、跨服务器、跨机器的同步问题。当你的应用是分布式的,或者在多个节点上运行时,就需要引入 分布式锁 来确保资源的共享和同步。

2.1 分布式锁的应用场景:
  • 多实例共享资源:当应用分布在多台服务器上,多个实例之间可能同时需要访问或修改某个共享的资源(如数据库记录、缓存、文件等),此时需要使用分布式锁来避免资源竞争和数据不一致。
  • 跨进程协调:分布式锁通常被用来解决不同进程或服务实例之间的资源竞争问题。
  • 确保唯一性和顺序性:分布式锁可以保证某些任务或操作只在一个节点上执行,避免重复执行任务或保证某些操作的顺序性(如定时任务、消息消费、数据库记录生成等)。
2.2 典型的分布式锁技术:
  • Redis:Redis 提供了强大的分布式锁功能,如基于 SETNXRedLock 算法等实现的锁,适合需要高性能和高可用性的分布式环境。
  • ZooKeeper:通过 ZooKeeper 的临时节点来实现分布式锁,适合需要强一致性和可靠性的分布式系统。
  • Etcd:类似 ZooKeeper,提供了强一致性保障,可以用来实现分布式锁。

3. 何时使用本地锁?

  • 应用只在单一 JVM 运行时:如果你的应用只运行在一台机器上,即使是多线程环境,也不需要考虑跨进程或跨机器的同步问题,那么使用本地锁(synchronizedReentrantLock)会更加简单和高效。
  • 性能要求较高:本地锁没有分布式锁的网络通信开销,通常会比分布式锁更高效。如果在单台机器上的并发控制足够满足需求,使用本地锁可以避免额外的分布式协调。

4. 何时使用分布式锁?

  • 应用部署在多台机器/多个服务实例中:当你的应用是分布式的,可能有多个服务实例或多个 JVM 节点在运行时,需要使用分布式锁来确保跨节点的资源访问同步。
  • 需要协调多个进程/服务实例之间的操作:如多个服务实例同时访问一个共享数据库表,或者多个应用实例处理相同的任务。
  • 避免重复执行任务:比如在分布式系统中有一些定时任务(例如,定时清理数据、批量任务等),分布式锁可以确保这些任务不会被多个服务实例同时执行。

5. 本地锁和分布式锁的权衡

本地锁(synchronized / ReentrantLock):
  • 优点
    • 使用简单。
    • 无网络延迟,性能高效。
    • 避免了分布式系统中的复杂性。
  • 缺点
    • 只能在同一 JVM 中有效,无法跨多个进程或机器同步。
分布式锁:
  • 优点
    • 可以在多个进程或服务实例间同步共享资源,保证数据一致性。
    • 适用于分布式环境中需要跨进程或跨服务进行同步的场景。
  • 缺点
    • 相对复杂,需要维护分布式系统的可靠性和一致性。
    • 存在性能开销(如网络延迟、锁争用等问题)。
    • 容易受到网络故障和服务宕机的影响。

6. 结论:两者的选择

  • 单机应用或单一 JVM 环境:如果你只在一个 JVM 进程中运行应用,synchronizedReentrantLock 是最好的选择,简洁高效,性能高。
  • 分布式应用或多节点环境:如果你的应用部署在多台机器、多个服务实例上,必须保证跨进程、跨机器的同步操作,那么就需要使用 分布式锁

总之,不同场景下使用不同的锁机制,不是越复杂的锁机制越好,而是要根据系统架构和业务需求来选择合适的同步方案。在 分布式系统 中, 分布式锁 是必不可少的,而在单一 JVM 中, 本地锁 是更为合适和高效的选择。

以下实现原理可以不看,B站连接 分布式锁教程



源码分析

   <T> RFuture<T> tryLockInnerAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {return this.evalWriteSyncedAsync(this.getRawName(), LongCodec.INSTANCE, command,"if ((redis.call('exists', KEYS[1]) == 0) or (redis.call('hexists', KEYS[1], ARGV[2]) == 1)) then redis.call('hincrby', KEYS[1], ARGV[2], 1); redis.call('pexpire', KEYS[1], ARGV[1]); return nil; end; return redis.call('pttl', KEYS[1]);", Collections.singletonList(this.getRawName()), new Object[]{unit.toMillis(leaseTime), this.getLockName(threadId)});}

这个方法 tryLockInnerAsync 是用来异步地尝试获取分布式锁的。它的作用是在 Redis 中执行一个 Lua 脚本来尝试获取一个锁,并返回一个 RFuture<T> 对象,代表一个异步操作的结果。具体来说,方法执行了一个 Redis 脚本来检查锁的状态,并设置过期时间和线程标识符。

方法解析

<T> RFuture<T> tryLockInnerAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command)
  • waitTime: 等待锁的最大时间(即最大尝试获取锁的时间)。
  • leaseTime: 锁的持有时间(即锁的过期时间)。
  • unit: 时间单位(如秒、毫秒等)。
  • threadId: 当前线程的标识符,用于标识锁的持有者。
  • command: 执行 Redis 命令的对象。

代码解析

return this.evalWriteSyncedAsync(this.getRawName(), LongCodec.INSTANCE, command, "if ((redis.call('exists', KEYS[1]) == 0) or (redis.call('hexists', KEYS[1], ARGV[2]) == 1)) then " +"redis.call('hincrby', KEYS[1], ARGV[2], 1); " +"redis.call('pexpire', KEYS[1], ARGV[1]); return nil; " +"end; return redis.call('pttl', KEYS[1]);", Collections.singletonList(this.getRawName()), new Object[]{unit.toMillis(leaseTime), this.getLockName(threadId)}
);

关键部分分析:

  1. evalWriteSyncedAsync:

    • evalWriteSyncedAsync 是一个异步执行 Redis Lua 脚本的方法。它会执行一个 Lua 脚本来进行 Redis 数据的操作。
    • this.getRawName() 获取 Redis 锁的键名。
    • LongCodec.INSTANCE 是用于处理返回结果的编解码器,表示返回类型是一个 Long 类型。
    • command 是 Redis 命令,用于进一步操作。
  2. Lua 脚本

    if ((redis.call('exists', KEYS[1]) == 0) or (redis.call('hexists', KEYS[1], ARGV[2]) == 1)) thenredis.call('hincrby', KEYS[1], ARGV[2], 1);redis.call('pexpire', KEYS[1], ARGV[1]);return nil;
    end;
    return redis.call('pttl', KEYS[1]);
    

    这段 Lua 脚本执行以下操作:

    • 检查锁是否存在
      • redis.call('exists', KEYS[1]) == 0: 检查锁是否存在(通过 Redis 键检查)。
      • redis.call('hexists', KEYS[1], ARGV[2]) == 1: 检查是否已经存在一个由 threadId 标识的锁。
    • 如果锁不存在或线程ID已经持有锁
      • redis.call('hincrby', KEYS[1], ARGV[2], 1): 增加该锁的计数,表示线程成功获得了锁。
      • redis.call('pexpire', KEYS[1], ARGV[1]): 设置锁的过期时间,ARGV[1] 是通过 unit.toMillis(leaseTime) 计算出的过期时间,单位是毫秒。
      • return nil: 如果获取锁成功,返回 nil
    • 如果锁存在且未获得,返回当前锁的剩余存活时间:
      • return redis.call('pttl', KEYS[1]): 返回当前锁的剩余过期时间,单位是毫秒。
  3. Collections.singletonList(this.getRawName()):

    • 传递锁的键名,这里 this.getRawName() 是锁的 Redis 键。
  4. new Object[]{unit.toMillis(leaseTime), this.getLockName(threadId)}:

    • ARGV[1] 是锁的过期时间(以毫秒为单位)。
    • ARGV[2] 是线程的标识符,用来区分不同线程的锁。

总结

这个方法的目的是在 Redis 中异步地尝试获取一个分布式锁。它通过执行一个 Lua 脚本来检查锁是否已经存在,如果锁不存在或者当前线程已经持有锁,它会将锁的计数加 1,并设置过期时间。如果锁已经被其他线程持有,脚本将返回锁的剩余存活时间。

RFuture<T> 是一种异步结果的返回类型,表示锁的获取过程会异步执行,调用者可以通过它来获取操作结果。

希望这解释能够帮助你更好地理解代码的功能和实现方式!

    private RFuture<Boolean> tryAcquireOnceAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId) {RFuture acquiredFuture;if (leaseTime > 0L) {acquiredFuture = this.tryLockInnerAsync(waitTime, leaseTime, unit, threadId, RedisCommands.EVAL_NULL_BOOLEAN);} else {acquiredFuture = this.tryLockInnerAsync(waitTime, this.internalLockLeaseTime, TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_NULL_BOOLEAN);}CompletionStage<Boolean> acquiredFuture = this.handleNoSync(threadId, acquiredFuture);CompletionStage<Boolean> f = acquiredFuture.thenApply((acquired) -> {if (acquired) {if (leaseTime > 0L) {this.internalLockLeaseTime = unit.toMillis(leaseTime);} else {this.scheduleExpirationRenewal(threadId);}}return acquired;});return new CompletableFutureWrapper(f);}

解释
这个方法 tryAcquireOnceAsync 是用来异步地尝试获取分布式锁的,并返回一个 RFuture<Boolean>,表示是否成功获取到锁。这个方法的核心功能是调用 tryLockInnerAsync 来请求锁,并根据是否成功获取锁来做进一步的处理。

代码解析

private RFuture<Boolean> tryAcquireOnceAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId) {RFuture acquiredFuture;if (leaseTime > 0L) {acquiredFuture = this.tryLockInnerAsync(waitTime, leaseTime, unit, threadId, RedisCommands.EVAL_NULL_BOOLEAN);} else {acquiredFuture = this.tryLockInnerAsync(waitTime, this.internalLockLeaseTime, TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_NULL_BOOLEAN);}CompletionStage<Boolean> acquiredFuture = this.handleNoSync(threadId, acquiredFuture);CompletionStage<Boolean> f = acquiredFuture.thenApply((acquired) -> {if (acquired) {if (leaseTime > 0L) {this.internalLockLeaseTime = unit.toMillis(leaseTime);} else {this.scheduleExpirationRenewal(threadId);}}return acquired;});return new CompletableFutureWrapper(f);
}

关键部分分析:

1. tryLockInnerAsync 调用
if (leaseTime > 0L) {acquiredFuture = this.tryLockInnerAsync(waitTime, leaseTime, unit, threadId, RedisCommands.EVAL_NULL_BOOLEAN);
} else {acquiredFuture = this.tryLockInnerAsync(waitTime, this.internalLockLeaseTime, TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_NULL_BOOLEAN);
}
  • tryLockInnerAsync: 这个方法是执行 Redis Lua 脚本来尝试获取锁的异步方法。这里根据 leaseTime 的值选择不同的超时设置:
    • 如果 leaseTime > 0L,则使用提供的 leaseTimeunit 来设置锁的持有时间。
    • 如果 leaseTime <= 0L,则使用 internalLockLeaseTime(可能是类的一个字段,代表默认的锁持有时间)。
2. handleNoSync 处理
CompletionStage<Boolean> acquiredFuture = this.handleNoSync(threadId, acquiredFuture);
  • handleNoSync 方法通常用来处理异步操作的后续逻辑,确保它不会同步阻塞线程。
  • 它可能是为了解决某些同步问题,比如锁在获取时可能需要做一些额外的处理。
3. thenApply 对锁获取结果的处理
CompletionStage<Boolean> f = acquiredFuture.thenApply((acquired) -> {if (acquired) {if (leaseTime > 0L) {this.internalLockLeaseTime = unit.toMillis(leaseTime);} else {this.scheduleExpirationRenewal(threadId);}}return acquired;
});
  • thenApply: 对 acquiredFuture 结果进行进一步处理。acquired 表示是否成功获取到锁。
    • 如果获取到锁:
      • 如果传入了正的 leaseTime,更新类中的 internalLockLeaseTime 字段。
      • 如果没有传入 leaseTime(即 leaseTime <= 0L),则调用 scheduleExpirationRenewal(threadId) 来安排续期操作,这意味着锁会在过期之前自动续期,保持锁的有效性。
4. 返回 CompletableFutureWrapper
return new CompletableFutureWrapper(f);
  • 最后,CompletableFutureWrapper 是对 CompletionStage<Boolean> 的包装,返回一个 RFuture<Boolean>。这确保了调用者能够异步地获取锁是否成功的结果。

总结:

  1. 获取锁的逻辑

    • 通过调用 tryLockInnerAsync 异步地尝试获取分布式锁。如果传入了 leaseTime,则使用提供的时间;否则,使用默认的锁持有时间(internalLockLeaseTime)。
  2. 异步处理结果

    • handleNoSync 处理异步结果,确保后续的操作不阻塞当前线程。
    • 使用 thenApply 处理锁获取成功后的逻辑,例如更新锁持有时间或安排锁续期。
  3. 返回结果

    • 最终通过 CompletableFutureWrapper 包装一个 CompletionStage<Boolean> 返回给调用者。

这个方法的设计是为了支持异步地获取分布式锁,并在获取锁后进行一些额外的处理,如设置锁的过期时间或安排续期。

可能的改进或问题:

  1. 变量重名问题
    在代码中,有一行声明:

    CompletionStage<Boolean> acquiredFuture = this.handleNoSync(threadId, acquiredFuture);
    

    这里的 acquiredFuture 是重复定义的,实际上应该避免在同一作用域中重名。你可以修改成:

    CompletionStage<Boolean> future = this.handleNoSync(threadId, acquiredFuture);
    
  2. 异常处理
    异常处理在这个方法中没有体现,如果在异步操作过程中发生错误,应该通过 .exceptionally 或其他手段进行捕获和处理,防止异步操作失败时没有任何反馈。

希望这些解释有助于你理解 tryAcquireOnceAsync 方法的实现!如果有更多问题,欢迎继续讨论。

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/61619.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

蓝桥杯每日真题 - 第19天

题目&#xff1a;&#xff08;费用报销&#xff09; 题目描述&#xff08;13届 C&C B组F题&#xff09; 解题思路&#xff1a; 1. 问题抽象 本问题可以看作一个限制条件较多的优化问题&#xff0c;核心是如何在金额和时间约束下选择最优方案&#xff1a; 动态规划是理想…

科研实验室的数字化转型:Spring Boot系统

1系统概述 1.1 研究背景 随着计算机技术的发展以及计算机网络的逐渐普及&#xff0c;互联网成为人们查找信息的重要场所&#xff0c;二十一世纪是信息的时代&#xff0c;所以信息的管理显得特别重要。因此&#xff0c;使用计算机来管理实验室管理系统的相关信息成为必然。开发合…

【Redis】持久化机制RDB与AOF

一、RDB RDB模式是就是将内存中的数据存储到磁盘中&#xff0c;等到连接断开的时候会进行持久化操作。但是如果服务器宕机&#xff0c;会导致这个持久化机制不会执行&#xff0c;但是内存中的文件会直接丢失。所以可以设置一个触发机制&#xff0c;save 60 1000 就是代表60秒 执…

Excel——宏教程(精简版)

一、宏的简介 1、什么是宏&#xff1f; Excel宏是一种自动化工具&#xff0c;它允许用户录制一系列操作并将其转换为VBA(Visual Basic for Applications)代码。这样&#xff0c;用户可以在需要时执行这些操作&#xff0c;以自动化Excel任务。 2、宏的优点 我们可以利用宏来…

【MyBatisPlus·最新教程】包含多个改造案例,常用注解、条件构造器、代码生成、静态工具、类型处理器、分页插件、自动填充字段

文章目录 一、MyBatis-Plus简介二、快速入门1、环境准备2、将mybatis项目改造成mybatis-plus项目&#xff08;1&#xff09;引入MybatisPlus依赖&#xff0c;代替MyBatis依赖&#xff08;2&#xff09;配置Mapper包扫描路径&#xff08;3&#xff09;定义Mapper接口并继承BaseM…

【spring】spring单例模式与锁对象作用域的分析

前言&#xff1a;spring默认是单例模式&#xff0c;这句话大家应该都不陌生&#xff1b;因为绝大多数都是使用单例模式&#xff0c;避免了某些问题&#xff0c;可能导致对某些场景缺乏思考。本文通过结合lock锁将单例模式、静态变量、锁对象等知识点串联起来。 文章目录 synchr…

Cyberchef使用功能之-多种压缩/解压缩操作对比

cyberchef的compression操作大类中有大量的压缩和解压缩操作&#xff0c;每种操作的功能和区别是什么&#xff0c;本章将进行讲解&#xff0c;作为我的专栏《Cyberchef 从入门到精通教程》中的一篇&#xff0c;详见这里。 关于文件格式和压缩算法的理论部分在之前的文章《压缩…

Elasticsearch开启认证及kibana密码登陆

Elasticsearch不允许root用户运行,使用root用户为其创建一个用户es,为用户es配置密码,并切换到es用户。 adduser elastic passwd elastic su elasticElasticsearch(简称ES)是一个基于Lucene的搜索服务器。它提供了一个分布式、多用户能力的全文搜索引擎,基于RESTful web…

C++初阶学习第十一弹——list的用法和模拟实现

目录 一、list的使用 二.list的模拟实现 三.总结 一、list的使用 list的底层是双向链表结构&#xff0c;双向链表中每个元素存储在互不相关的独立节点中&#xff0c;在节点中通过指针指向 其前一个元素和后一个元素。 常见的list的函数的使用 std::list<int> It {1,…

Postman之安装及汉化基本使用介绍

系列文章目录 1.Postman之安装及汉化基本使用介绍 2.Postman之变量操作 3.Postman之数据提取 4.Postman之pm.test断言操作 5.Postman之newman Postman之安装及汉化 1.安装及汉化postman2.基本使用介绍2.1.基本功能&#xff1a;2.2.编辑、查看、设置环境、全局、集合变量2.3.复制…

VUE 指令 事件绑定,.stop阻止冒泡

1、VUE 的模板语法和指令 目的增强html的功能 所有的指令以自定义属性的方式去写 v-xxx ,指令就是vue提供给我们能够更方便将数据和页面展示出来的操作&#xff0c;具体就是以数据去驱动DOM ,简化DOM操作的行为。 2、内容渲染指令 ① {{}} 模板渲染&#xff08;模板引擎&am…

算法.图论-习题全集(Updating)

文章目录 本节设置的意义并查集篇并查集简介以及常见技巧并查集板子(洛谷)情侣牵手问题相似的字符串组岛屿数量(并查集做法)省份数量移除最多的同行或同列石头最大的人工岛找出知晓秘密的所有专家 建图及其拓扑排序篇链式前向星建图板子课程表 本节设置的意义 主要就是为了复习…

易语言学习-cnblog

易语言数据类型 数值转换命令&#xff08;自己学&#xff09; 数值到大写&#xff08;&#xff09;将一个数值转换到中文读法&#xff0c;第二个参数为是否为简体。 数值到大写&#xff08;123.44&#xff0c;假&#xff09; 猜测结果 数值到金额&#xff08;&#xff09;将双…

树莓派的开机自启

前言 很多比赛你的装置是不能动的,就是你直接拿上去,不允许用电脑启动. 树莓派开机自启的三种方式 1,快捷方式自启动 就是在我们用户的目录下(他这里是/home/pi,我的是/home/zw),ctrlh可以显示隐藏文件价, #没有就找你自己的用户目录 cd /.config#在这里面建一个autostart文…

Day10_CSS过度动画

Day10_CSS过度动画 背景 : PC和APP项目我们已经开发完毕, 但是再真正开发的时候有些有些简易的动态效果我们可以使用CSS完成 ; 本节课我们来使用CSS完成基础的动画效果 今日学习目标 CSS3过度CSS3平面动态效果CSS3动画效果案例 1. CSS3过渡 ​ 含义 :过渡指的是元素从一种…

go语言闭包捕获的是变量的引用而不是变量的值

在 Go 语言中&#xff0c;闭包捕获的是变量的引用&#xff0c;而不是变量的值。这意味着闭包会引用循环变量或外部变量的实际内存位置&#xff0c;而不是在闭包创建时复制变量的值。这种行为有时会导致意外的结果&#xff0c;尤其是在循环中创建多个闭包时。 闭包捕获变量的引…

操作系统进程和线程——针对实习面试

目录 操作系统进程和线程什么是进程和线程&#xff1f;进程和线程的区别&#xff1f;进程有哪些状态&#xff1f;什么是线程安全&#xff1f;如何实现线程安全&#xff1f;什么是线程安全&#xff1f;如何实现线程安全&#xff1f; 进程间的通信有哪几种方式&#xff1f;什么是…

MyBatis——#{} 和 ${} 的区别和动态 SQL

1. #{} 和 ${} 的区别 为了方便&#xff0c;接下来使用注解方式来演示&#xff1a; #{} 的 SQL 语句中的参数是用过 ? 来起到类似于占位符的作用&#xff0c;而 ${} 是直接进行参数替换&#xff0c;这种直接替换的即时 SQL 就可能会出现一个问题 当传入一个字符串时&#xff…

图像处理 之 凸包和最小外围轮廓生成

“ 最小包围轮廓之美” 一起来欣赏图形之美~ 1.原始图片 男人牵着机器狗 2.轮廓提取 轮廓提取 3.最小包围轮廓 最小包围轮廓 4.凸包 凸包 5.凸包和最小包围轮廓的合照 凸包和最小包围轮廓的合照 上述图片中凸包、最小外围轮廓效果为作者实现算法生成。 图形几何之美系列&#…

美畅物联丨智能分析,安全管控:视频汇聚平台助力智慧工地建设

随着科技的持续发展&#xff0c;建筑行业正朝着智能化的方向迅猛迈进。智慧工地作为建筑行业智能化的关键体现形式&#xff0c;借助各类先进技术来提升工地的管理效率、安全性以及生产效益。在这个过程中&#xff0c;视频汇聚平台发挥着极为重要的作用。以畅联AIoT开放云平台为…