Redis实战案例15-基于Redis实现分布式锁

1. 初级版本

在这里插入图片描述

注意自动拆箱时的空指针异常

public class SimpleRedisLock implements ILock{private StringRedisTemplate stringRedisTemplate;private String lockName;private static final String KEY_PREFIX = "lock:";public SimpleRedisLock(StringRedisTemplate stringRedisTemplate, String lockName) {this.stringRedisTemplate = stringRedisTemplate;this.lockName = lockName;}@Overridepublic boolean tryLock(long timeoutSec) {// 获取线程标识long threadId = Thread.currentThread().getId();// 获取锁Boolean success = stringRedisTemplate.opsForValue().setIfAbsent(KEY_PREFIX + lockName, threadId + "", timeoutSec, TimeUnit.SECONDS);// 自动拆箱存在null异常,不要直接返回success,做一下判断return Boolean.TRUE.equals(success);}@Overridepublic void unlock() {stringRedisTemplate.delete(KEY_PREFIX + lockName);}
}

之前synchronized方法修改为

// 一人一单
// 用户id
Long userId = UserHolder.getUser().getId();
synchronized (userId.toString().intern()) {// 获取代理对象IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();return proxy.createVoucherOrder(voucherId);
}
// 一人一单
// 用户id
Long userId = UserHolder.getUser().getId();
// 创建锁对象(注意要拼接用户id,实现单个用户的一人一单,只有同一个id的请求打来时才要进行锁定)
SimpleRedisLock lock = new SimpleRedisLock(stringRedisTemplate, "order:" + userId);
// 获取锁(稍微设置久一点,避免测试时锁失效)
boolean isLock = lock.tryLock(1200);
if(!isLock){return Result.fail("一人只能抢一次哦~");
}
try {// 获取IVoucherOrderService的代理对象IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();return proxy.createVoucherOrder(voucherId);
} finally {lock.unlock();
}

2. 初级版本存在的问题(重点)

在这里插入图片描述

当线程1获取锁时,发生业务阻塞,阻塞时间甚至超过了锁的释放时间,这时线程1会失去锁资源;

在这里插入图片描述

此时,线程2可以获取锁资源,并执行自己的业务。此时线程1获得了某些资源之后不再阻塞,开始执行自己的业务,并且执行完之后会进行锁的释放操作;
上述情况出现时,就会触发安全问题,线程1会把线程2的锁给释放掉,线程3也可以拿到锁了,锁机制也就相当于是失效了;

在这里插入图片描述

解决办法:给锁加标识(有点像乐观锁),线程在释放锁时多进行一次判断

在这里插入图片描述

流程图进行相应修改

在这里插入图片描述

3. 代码实现优化版本

采用UUID是因为每个JVM都会维护线程id的递增数字,如果直接采用线程id可能出现线程id冲突,使用UUID在集群环境下更合适;

在这里插入图片描述

public class SimpleRedisLock implements ILock{private StringRedisTemplate stringRedisTemplate;private String lockName;private static final String KEY_PREFIX = "lock:";// 参数true去掉UUID自动给的下划线private static final String ID_PREFIX = UUID.randomUUID().toString(true) + "-";public SimpleRedisLock(StringRedisTemplate stringRedisTemplate, String lockName) {this.stringRedisTemplate = stringRedisTemplate;this.lockName = lockName;}@Overridepublic boolean tryLock(long timeoutSec) {// 获取线程标识,拼接上对应的UUIDString threadId = ID_PREFIX + Thread.currentThread().getId();// 获取锁Boolean success = stringRedisTemplate.opsForValue().setIfAbsent(KEY_PREFIX + lockName, threadId + "", timeoutSec, TimeUnit.SECONDS);// 自动拆箱存在null异常,不要直接返回success,做一下判断return Boolean.TRUE.equals(success);}@Overridepublic void unlock() {// 获取线程标识String threadId = ID_PREFIX + Thread.currentThread().getId();// 获取锁中的线程标识String id = stringRedisTemplate.opsForValue().get(KEY_PREFIX + lockName);// 判断标识是否一致if(threadId.equals(id)) {// 释放锁stringRedisTemplate.delete(KEY_PREFIX + lockName);}}
}

4. 上述代码的原子性问题

在使用分布式锁的情况下,当一个线程持有锁时,其他线程需要等待该锁被释放才能获取到它。
然而,垃圾回收的发生可能会导致线程在释放锁之前出现延迟或暂停。具体来说,当垃圾回收器运行时,它会检查和清理不再被引用的对象,回收内存资源。在这个过程中,所有线程的执行都会被暂停,称为“停顿时间”。从而影响其他线程的等待时间;
如果一个线程在持有锁的过程中发生了垃圾回收的停顿,它可能无法及时释放锁,从而延长了其他线程等待锁的时间。并且可能存在锁到期自己释放了的情况;

在这里插入图片描述
例如:当上述代码进行释放锁的操作时,已经做完判断标识是否一致的if语句之后,在准备释放锁之前发送了阻塞(GC回收阻塞)。此时锁到期自动释放,线程2从而获取到锁资源,而恰好线程1恢复开始执行业务,注意,此时的线程1是已经判断完了标识一致操作之后才阻塞的,所以线程1会直接进行释放锁的操作,这样线程2获取的锁会被线程1释放掉

// 判断标识是否一致if(threadId.equals(id)) {// 释放锁stringRedisTemplate.delete(KEY_PREFIX + lockName);}}

所以要确保加锁和释放锁具有原子性

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

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

相关文章

设计模式——代理模式

代理模式(静态代理) 普通代理模式的核心就是,被代理对象和代理对象(共属一个接口),被代理对象要执行的操作由代理对象完成(此时被代理者需要手动new出来,再传递给代理者&#xff09…

浅谈关于智慧校园安全用电监测系统的设计

0引言 人生人身安全是大家关注的话题,2019年12月中国消防统计近五年发生在全国学生宿舍的火灾2314起(中国消防2019.12.应急管理部消防救援局官方微博),违规电器是引发火灾的主因。如果在各寝室安装智能用电监测器实时监督线路参数…

IntelliJ IDEA安装教程

一、下载安装包 首先进入IDEA官网下载2021.2.1版本的安装包,不要问我为什么不下最新版,后面我会说。 二、安装与配置 打开安装包,安装完成后选择Evaluate for free(免费试用),创建一个项目,进入…

ELK 企业级日志分析系统----elk的部署

文章目录 一、ELK 简介1.1 ElasticSearch介绍1.2 ELK的组件ElasticSearchKiabanaLogstash可以添加的其它组件 1.3 为什么要使用 ELK1.4 完整日志系统基本特征1.5 ELK 的工作原理: 二、elk环境部署2.1 ELK Elasticsearch 集群部署(在Node1、Node2节点上操…

Django_模板标签语法

目录 引用变量 for循环标签 if条件标签 with标签 注释 extends和block标签 csrf_token标签 load static标签 源码等资料获取方法 引用变量 可以使用{{}}引用视图函数响应的变量和模板中的变量。 比如有如下视图函数 在模板中引用变量方式如下 界面展示如下 for循环标…

【Rust日报】2023-07-10 Flutter 中使用 Rust

Rust In Flutter 这个 high-level 封装包简化了Rust集成到你的Flutter应用程序中的过程,无需代码生成或本地工具。它考虑到易用性、未来的可扩展性和卓越的性能,处理所有复杂的后台工作。只需将此包添加到您的 Flutter 项目中,你就可以开始编…

Simulink 自动代码生成System Composer的使用Architecture Model

目录 前言 已有模型转换成架构模型(Architecture Model) 架构模型生成代码步骤 总结 前言 前面已经介绍了AUTOSAR架构模型怎么去搭建,参考下文: Simulink代码生成:Autosar模型及代码生成_simulink autosar_卡洛斯…

STM32 Proteus仿真水箱水塔水位温度控制系统DS18B20 -0065

STM32 Proteus仿真水箱水塔水位温度控制系统DS18B20 -0065 Proteus仿真小实验: STM32 Proteus仿真水箱水塔水位温度控制系统DS18B20 -0065 功能: 硬件组成:STM32F103C8单片机 LCD1602显示器ADC220V转3.3V电路DS18B20温度多个按键&#xf…

mac上 如何批量在文件名中插入文字

mac上 如何批量在文件名中插入文字?在使用Mac电脑的时候,我们经常需要对大量文件的名称进行修改,例如需要在大量文件的名称中插入一些相同的文字或者字符的时候,你会用什么方法来完成这项工作呢?相信很多人就面对过类似…

校园网免认证/校园网pojie

我们的目标是xiao yuan wang pojie 我们使用一个简单的python脚本,用于jiechu /pojie校园网只能登录一台手机和一台电脑的限制,仅供学习。 原理 我们利用已有可正常上网的校园网账户作为跳板,连上网后在后台下线账号所登录的设备&#xff0…

Python3,6行代码,搞定网络测速神器,我直接卸载某60测速器。

6行代码搞定网络测速器 1、引言2、代码实战2.1 介绍2.2 安装2.3 示例 3、总结 1、引言 小屌丝:鱼哥,你知道speedtest 这个库吗? 小鱼:嗯,知道一点点,咋了? 小屌丝:那你教教我怎么使…

【输入法篇】关闭微软输入法烦人的必应的文本建议设置

【输入法篇】关闭微软输入法烦人的必应的文本建议设置 额,微软输入法设置路径藏得有点深,建议在设置里面直接搜索进入,方便些!—【蘇小沐】 文章目录 【输入法篇】关闭微软输入法烦人的必应的文本建议设置1.实验环境 &#xff08…