本地锁 & 分布式锁
引子: 解决缓存击穿问题
synchronized (this){代码块}
public synchronized Map<String,List<Catelog2Vo>> getCatalog]sonFromDb()
:SpringBoot所有的组件在容器中都是单例的。
优点:速度快
所以这样只能有一个人拿到锁进入逻辑, 但是也是在单体服务下可行,在分布条件下,几个服务就生成几把锁(应该问题不大)
测试1: 有3个人进入拿到了锁
原因: 拿到锁的那个线程确认缓存没有,去查数据库,查询后释放锁 ,刚释放还没来得及往缓存中存数据, 另一个线程就拿到锁再进行操作
解决方案: 把 (往缓存中存数据) 放到锁中 [稍微影响效率]
这里推荐下阿里的jetcache 全部都封装好了
redis分布式锁
分布式中,即使多个服务,也只让一个人去查查数据
原理: redis set hello world NX
先看缓冲中有没有, 没有的话就占分布式锁,执行下面的逻辑
可能遇到问题
1.拿到锁之后,执行完逻辑,在释放锁之前突然断电,无法释放锁导致其他服务无法获取,造成死锁 (解决办法: 锁设置过期时间:30S)
还有问题: 若是刚加锁,还没来得及设置过期时间,就闪断
解决办法: 原子操作
Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", "111",30, TimeUnit.SECONDS);
删锁的时候遇到问题
1.假设业务时间过长,锁自动过期了 就会有其他线程进来
2.自己的锁过期了,删掉了其他人正在用的锁
解决办法:可以加V值改为UUID,删锁执行判断UUID进行删除
String uuid = UUID.randomUUID().toString();
新问题(无原子性): 因为数据传输慢的原因,去redis判断uuid是否是自己,判断后,在然后往回传结果true的路上 ,K过期了,其他人占了锁 , 此时,删除的锁,也是别人的锁
解决办法: 删锁的原子性,利用lua脚本
String luaScript = "if redis.call('get',KEYS[1]) == ARGV[1]\n" + "then\n" + " return redis.call('del',KEYS[1])\n" + "else\n" + " return 0\n" + "end"; // 删除锁 redisTemplate.execute(new DefaultRedisScript<Long>(luaScript, Long.class), Arrays.asList("LOCK_KEY_CATALOG_JSON"), uuid);
Arrays.asList("LOCK_KEY_CATALOG_JSON") == uid 就删除锁
加锁保证原子性,解锁保证原子性
问题: 锁的自动续期问题, [把时间放长一点]
导入依赖

<dependency> <groupId>org.redisson</groupId> <artifactId>redisson</artifactId> <version>3.13.3</version> </dependency>
配置

@Configuration public class MyRedissonConfig { /** * 注入客户端实例对象 */ @Bean(destroyMethod="shutdown") public RedissonClient redisson(@Value("${spring.redis.host}") String host, @Value("${spring.redis.port}")String port) throws IOException { // 1.创建配置 Config config = new Config(); config.useSingleServer().setAddress("redis://" + host + ":" + port);// 单节点模式 // config.useSingleServer().setAddress("rediss://" + host + ":" + port);// 使用安全连接 // config.useClusterServers().addNodeAddress("127.0.0.1:7004", "127.0.0.1:7001");// 集群模式 // 2.创建redisson客户端实例 RedissonClient redissonClient = Redisson.create(config); return redissonClient; } }
测试
RedissonClient redissonClient
实现分布式锁
锁的自动续期+30S (看门狗 : 10秒续一次) ,一旦设置过期时间,看门狗会失效
可重入锁(抢占一把锁)

public Map<String, List<Catalog2VO>> getCatalogJsonFromDBWithRedissonLock() { // 1.抢占分布式锁,同时设置过期时间 RLock lock = redisson.getLock("LOCK_KEY_CATALOG_JSON"); lock.lock(30, TimeUnit.SECONDS); try { // 2.查询DB Map<String, List<Catalog2VO>> result = getCatalogJsonFromDB(); return result; } finally { // 3.释放锁 lock.unlock(); } }
RLock lock = redisson.getLock("anyLock"); // 最常见的使用方法 lock.lock(); // 加锁以后10秒钟自动解锁 // 无需调用unlock方法手动解锁 lock.lock(10, TimeUnit.SECONDS); // 尝试加锁,最多等待100秒,上锁以后10秒自动解锁 boolean res = lock.tryLock(100, 10, TimeUnit.SECONDS); if (res) { try { ... } finally { lock.unlock(); } }
公平锁(先到先得)
//所有请求线程会在一个队列中排队,当某个线程出现宕机时,Redisson会等待5秒后继续下一个线程,
也就是说如果前面有5个线程都处于等待状态,那么后面的线程会等待至少25秒。 RLock fairLock = redisson.getFairLock("anyLock"); // 最常见的使用方法 fairLock.lock(); // 10秒钟以后自动解锁 // 无需调用unlock方法手动解锁 fairLock.lock(10, TimeUnit.SECONDS); // 尝试加锁,最多等待100秒,上锁以后10秒自动解锁 boolean res = fairLock.tryLock(100, 10, TimeUnit.SECONDS); ... fairLock.unlock();
读写锁(写锁控制读锁)
RReadWriteLock rwlock = redisson.getReadWriteLock("anyRWLock"); // 最常见的使用方法 rwlock.readLock().lock(); // 或 rwlock.writeLock().lock(); // 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(); //抽象lock(代表读写锁)
闭锁
等待全部成才能加锁
RCountDownLatch latch = redisson.getCountDownLatch("anyCountDownLatch"); latch.trySetCount(5); //等5个走完才结束 latch.await(); // 在其他线程或其他JVM里() RCountDownLatch latch = redisson.getCountDownLatch("anyCountDownLatch"); latch.countDown();
信号量
做限流
提前在redis中存入信号量{"semaphore":"10"} RSemaphore semaphore = redisson.getSemaphore("semaphore"); semaphore.acquire(); //获取信号量-1,获取不到就一直等待
semaphore.tryAcquire(23, TimeUnit.SECONDS); /.尝试等待23秒,
//其他线程 semaphore.release(); //释放信号量+1
可过期性信号量
红锁(大多数锁上就行)
注意锁的粒度
办法: 要么加锁牺牲效率, 要么允许暂时的不一致,有最终一致性就行 丨(加了缓存过期时间)
问题: 脏数据问题, 2号慢 , 3号读到了脏数据,也更新了脏数据 丨 (加了缓存过期时间)
经常修改的数据,直接读数据库就行
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?