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 ... 
随机推荐
- 关于tensorflow无法使用gpu
			python3.6 无法使用tensorflow gpu 环境名称 test1 在控制台里进入环境 conda activate test1 使用python python 查看gpu能否使用 pri ... 
- SQLserver 2014自定义备份数据库
			一.管理-维护计划-维护计划向导-下一步 二.点击更改设置任务执行时间-确定-下一步 三.选择备份数据库完整-下一步 四.选择需要备份的数据库-然后确定 五.点目标自定义备份文件存储目录-下一步 六. ... 
- ciscn_2019_s_3  一道收获很多的题(进步大只能说明基础差)
			32位与64位 系统调用的区别: 1. 传参方式不同 2. 系统调用号 不同 3. 调用方式 不同 32位: 传参方式:首先将系统调用号 传入 eax,然后将参数 从左到右 依次存入 ebx,ecx, ... 
- EhCache简单入门
			一 介绍 EhCache 是一个纯Java的进程内缓存框架,具有快速.精干等特点,是Hibernate中默认CacheProvider.Ehcache是一种广泛使用的开源Java分布式缓存.主要面向通 ... 
- java 多线程 发布订阅模式:发布者java.util.concurrent.SubmissionPublisher;订阅者java.util.concurrent.Flow.Subscriber
			1,什么是发布订阅模式? 在软件架构中,发布订阅是一种消息范式,消息的发送者(称为发布者)不会将消息直接发送给特定的接收者(称为订阅者).而是将发布的消息分为不同的类别,无需了解哪些订阅者(如果有的话 ... 
- js Uncaught TypeError: undefined is not a function
			如下代码: var columns={}; var column={}: column.name='张三'; columns.push(column); 会出现Uncaught TypeError: ... 
- 【LeetCode】1423. 可获得的最大点数 Maximum Points You Can Obtain from Cards (Python)
			作者: 负雪明烛 id: fuxuemingzhu 公众号:每日算法题 本文关键词:LeetCode,力扣,算法,算法题,滑动窗口,递归,前缀和,preSum,刷题群 目录 题目描述 解题思路 递归 ... 
- 【LeetCode】594. Longest Harmonious Subsequence 解题报告(Python & C++)
			作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 统计次数 日期 题目地址:https://leetc ... 
- 【LeetCode】215. Kth Largest Element in an Array 解题报告(Python & C++)
			作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 方法一:移除最大值 方法二:排序 方法三:大顶堆 方 ... 
- 1307 - Counting Triangles
			1307 - Counting Triangles PDF (English) Statistics Forum Time Limit: 2 second(s) Memory Limit: 32 ... 
