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 ...
随机推荐
- Redis - 2 - 聊聊Redis的RDB和AOF持久化 - 更新完毕
1.RDB 1.1).RDB是什么? RDB,全称Redis Database RDB是Redis进行持久化的一种方式,当然:Redis默认的持久化方式也是RDB 1.2).Redis配置RDB 1. ...
- 企业级BI是自研还是采购?
企业级BI是自研还是采购? 上一篇<企业级BI为什么这么难做?>,谈到了企业级BI项目所具有的特殊背景,以及在"破局"方面的一点思考,其中谈论的焦点主要是在IT开发项目 ...
- Nginx开启php_info
目录 一.简介 二.配置 三.参数 一.简介 pathinfo是php需要的东西,php可以用这个函数来获得信息. http://wangying.sinaapp.com/tools/index.ph ...
- Nginx模块之ngx_http_gzip_module
Module ngx_http_gzip_module 该ngx_http_gzip_module模块是一个使用"gzip"方法压缩响应的过滤器.这通常有助于将传输数据的大小减少一 ...
- proxy跨域
跨域 浏览器访问非同源的网址时,会被限制访问,出现跨域问题. 解决方案: 1.response 添加 header(CORS) 2.JSONP 方式 3.全局对象+iframe (1)document ...
- [BUUCTF]PWN——jarvisoj_level3
jarvisoj_level3 附件 步骤 例行检查,32位,nx保护 运行一下程序 32位ida载入,shift+f12没有看到程序里有可以直接利用的后面函数,根据运行时的字符串找到了程序的关键函数 ...
- git 省略 commit message
每次提交使用 git commit --allow-empty-message --no-edit 也可以设置命令别名 git config --global alias.nocommit " ...
- CF1041B Buying a TV Set 题解
Content 给定四个数 \(a,b,c,d\),求满足以下条件的数对 \((x,y)\) 的个数: \(x\leqslant a,y\leqslant b\). \(\dfrac{x}{y}=\d ...
- RPA培训:RPA的核心三个组件常见部署方式(RPA学习天地)
整体架构 目前主流厂商的RPA平台就是由控制台.设计器和机器人这三个标准套件组成,这三个核心套件形成了RPA产品的基本要素.其它如AI平台.人机交互.流程挖掘.自动化中心等都是衍生出来的周边产品. 1 ...
- 【LeetCode】947. Most Stones Removed with Same Row or Column 解题报告(Python & C++)
作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 并查集 日期 题目地址:https://leetco ...