redis 加锁与解锁的详细总结,解决线程并发导致脏数据
1.前言
对每个controller来说都是全新且单独的,原因是多线程,如果多个请求操作共有的数据,这样的并发操作会导致脏数据
怎么解决?
mysql可以使用积极锁解决,
这里讲解的是redis的解决办法,虽然有几种解决办法,但我这里只记录最好的:setnx指令算法加锁,思路与mysql的消极锁相似
2.redis锁需要满足几个要求:
(1)只能让一个客户端加锁,当锁存在时其他客户端不可以加锁
(2)只能让加锁的客户端解锁,不允许其他客户端解锁
(3)当锁存在时,加锁失败的客户端需要等待解锁后自己加锁,只有自己加锁成功后才可以操作共有数据,即阻塞操作
(4)不能产生死锁,如果加锁的客户端还没有解锁前就因为某些原因就崩溃了,锁自动解锁,不影响其他客户端操作
3.原理
(1)加锁 则是 使用setnx指令,新建一个键值对,如果成功会返回 OK ,即视为加锁成功,如果已经存在,则返回空 ,视为加锁失败
(2)为了确保不产生死锁,应该对这个数据设置存活时间,如果崩溃后,到时间会自动删除
(3)解锁 则是 使用Lua脚本语句对键值对判断这个锁是不是自己加的,如果时别人的或者不存在,则不操作,如果是自己的则做删除键值对操作
(4)无论时加锁还是解锁操作,为了确保逻辑的原子性,都应该是个多参数操作指令,有些低版本的redis不支持,才会将指令拆分成多条顺序执行
但是容易导致逻辑问题,形成脏数据,
4.封装工具
我做的一个工具类,输入参数即可完成加锁 解锁操作 ,这是单机的,如果是分布式则改将jedis对象改成分布式对象ShardedJedisPool即可,思路一样
package cn.cen2guo.clinic.redis; import redis.clients.jedis.Jedis; import java.util.Collections; public class JedisLock {
//加锁检查
//加锁标识,返回结果是这个则说明加锁成功
private static final String LOCK_SUCCESS = "OK";
//定义set方法的使用方式,如果不存在则以 lockKey 为 key ,requestId 为value 新建string类型键值 ,
private static final String SET_IF_NOT_EXIST = "NX";
//表示开启存活时间,PX是毫秒数 ,如果想以秒为单位则设为EX
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) {
//语句意思是,判断lockKey 这个key是否存在,不存在则以 lockKey 为 key ,requestId 为value 新建string类型键值 ,
//并开启存活时间,单位毫秒,
//新建成功返回结果 OK ,说明加锁成功
// 失败则为空,说明加锁失败
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) {
// 使用Lua脚本,执行判断语句,
//删除成功则返回结果1L,失败则为0
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));
System.out.println("解锁结果是=="+result);
if (RELEASE_SUCCESS.equals(result)) {
return true;
}
return false;
}
}
JedisLock
5.使用
加锁
/**
* 给等待池加锁
*/
//锁的key名,自定义
String lockKey = "lock" ;
//唯一识别是哪个用户端的锁id
// 标准的UUID格式为:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx (8-4-4-4-12)。
//时间戳+随机数
String requestId = System.currentTimeMillis() + UUID.randomUUID().toString();
//存活时间,10秒,防止崩溃造成死锁
int expireTime = 10000;
//获取jedis对象
Jedis jedis = jedisMyGetJedis.myGetJedis();
//加锁操作
boolean f = JedisLock.tryGetDistributedLock(jedis, lockKey, requestId, expireTime);
if (!f) {
// 失败,锁已经存在
//休眠0.5秒后再次加锁操作
System.out.println("加锁失败,锁已经存在" + new Date());
int k = 0;
while (k == 0) {
System.out.println("睡眠一次" + new Date());
//当前线程休眠0.5秒
Thread.sleep(500);
boolean f2 = JedisLock.tryGetDistributedLock(jedis, lockKey, requestId, expireTime);
if (f2) {
//加锁成功
// 退出循环
k = 1;
}
}
}
//加锁成功
System.out.println("加锁成功" + new Date());
//下面的操作只会有一个人操作,因此不需要担心有并发操作
解锁
//解锁
boolean r = JedisLock.releaseDistributedLock(jedis, lockKey, requestId);
if (r) {
System.out.println("解锁成功");
} else {
System.out.println("解锁失败");
}
//关闭jedis对象
jedis.close();
redis 加锁与解锁的详细总结,解决线程并发导致脏数据的更多相关文章
- Redis加锁与解锁
Redis加锁 customerM = BaseMemCached.setMLock(customerId); /** * 个人账户表加锁 **/ public static CustomerM se ...
- PHP中redis加锁和解锁的简单实现
背景说明 在程序开发过程中,通常会遇到需要独占式的访问一些资源的情形,比如商品秒杀时扣减库存.这时就需要对资源加锁.实现锁的方式有很多,比如数据库锁.文件锁等等.本文简单介绍PHP中使用redis来实 ...
- java——多线程的实现方式、三种办法解决线程赛跑、多线程数据同步(synchronized)、死锁
多线程的实现方式:demo1.demo2 demo1:继承Thread类,重写run()方法 package thread_test; public class ThreadDemo1 extends ...
- LVS解决高并发,大数据量
http://www.360doc.com/content/14/0726/00/11962419_397102114.shtml LVS的全称Linux vitual system,是由目前阿里巴巴 ...
- 这或许是最详细的JUC多线程并发总结
多线程进阶---JUC并发编程 完整代码传送门,见文章末尾 1.Lock锁(重点) 传统 Synchronizd package com.godfrey.demo01; /** * descripti ...
- sql语句对数据库表进行加锁和解锁
锁是数据库中的一个非常重要的概念,它主要用于多用户环境下保证数据库完整性和一致性. 我们知道,多个用户能够同时操纵同一个数据库中的数据,会发生数据不一致现象.即如果没有锁定且多个用户同时访问一个数据库 ...
- redis加锁
1. redis加锁分类 redis能用的的加锁命令分表是INCR.SETNX.SET2. 第一种锁命令INCR 这种加锁的思路是, key 不存在,那么 key 的值会先被初始化为 0 ,然后再执行 ...
- redis加锁的几种实现
redis加锁的几种实现 2017/09/21 1. redis加锁分类 redis能用的的加锁命令分表是INCR.SETNX.SET 2. 第一种锁命令INCR 这种加锁的思路是, key 不存在, ...
- Java 使用Redis缓存工具的图文详细方法
开始在 Java 中使用 Redis 前, 我们需要确保已经安装了 redis 服务及 Java redis 驱动,且你的机器上能正常使用 Java. (1)Java的安装配置可以参考我们的 Java ...
随机推荐
- 13.Vue.js 组件
组件(Component)是 Vue.js 最强大的功能之一. 组件可以扩展 HTML 元素,封装可重用的代码. 组件系统让我们可以用独立可复用的小组件来构建大型应用,几乎任意类型的应用的界面都可以抽 ...
- 【C/C++】二维数组的传参的方法/二维字符数组的声明,使用,输入,传参
[问题] 定义了一个子函数,传参的内容是一个二维数组 编译提示错误 因为多维数组作为形参传入时,必须声明除第一位维外的确定值,否则系统无法编译(算不出偏移地址) [二维数组的传参] 方法一:形参为二维 ...
- python实现skywalking的trace模块过滤和报警
skywalking本身的报警功能,用起来视乎不是特别好用,目前想实现对skywalking的trace中的错误接口进行过滤并报警通知管理员和开发.所以自己就用python对skywalking做了二 ...
- Mysql-5.6 二进制多实例部署
目录 一.简介 二.环境声明 三.程序部署 一.简介 MySQL多实例就是在一台机器上开启多个不同的服务端口(如:3306,3307),运行多个MySQL服务进程,通过不同的socket监听不同的服务 ...
- MySQL如何把varchar类型字段转换成int类型进行倒叙排序
SELECT * FROM sheet2 t1 WHERE t1.`金额`+'0' ORDER BY t1.`金额` DESC;
- Svelte入门——Web Components实现跨框架组件复用(二)
在上节中,我们一起了解了如何使用Svelte封装Web Component,从而实现在不同页面间使用电子表格组件. Svelte封装组件跨框架复用,带来的好处也十分明显: 1.使用框架开发,更容易维护 ...
- [BUUCTF]REVERSE——[FlareOn4]login
[FlareOn4]login 附件 步骤: 是个网页,直接打开,查看网页源码 百度了几个函数 charCodeAt(0)是返回当前字符的Unicode 编码 String.fromCharCode返 ...
- LuoguP7714 「EZEC-10」排列排序 题解
Content 给定一个 \(1\sim n\) 的一个排列 \(p\),你每次可以选择一个区间 \([l,r]\) 并花费 \(r-l+1\) 的代价将下标在这个区间内的所有数升序排序,求使得排列 ...
- CF20C Dijkstra? 题解
Content 给定一张 \(n\) 个点 \(m\) 条边的无向图,请判断是否有一条可行的从 \(1\) 到 \(n\) 的路径,有的话输出长度最短的,没有的话输出 -1. 数据范围:\(2\leq ...
- 二、Uniapp+vue+腾讯IM+腾讯音视频开发仿微信的IM聊天APP,支持各类消息收发,音视频通话,附vue实现源码(已开源)-腾讯云后台配置TXIM
项目文章索引 1.项目引言 2.腾讯云后台配置TXIM 3.配置项目并实现IM登录 4.会话好友列表的实现 5.聊天输入框的实现 6.聊天界面容器的实现 7.聊天消息项的实现 8.聊天输入框扩展面板的 ...