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 加锁与解锁的详细总结,解决线程并发导致脏数据的更多相关文章

  1. Redis加锁与解锁

    Redis加锁 customerM = BaseMemCached.setMLock(customerId); /** * 个人账户表加锁 **/ public static CustomerM se ...

  2. PHP中redis加锁和解锁的简单实现

    背景说明 在程序开发过程中,通常会遇到需要独占式的访问一些资源的情形,比如商品秒杀时扣减库存.这时就需要对资源加锁.实现锁的方式有很多,比如数据库锁.文件锁等等.本文简单介绍PHP中使用redis来实 ...

  3. java——多线程的实现方式、三种办法解决线程赛跑、多线程数据同步(synchronized)、死锁

    多线程的实现方式:demo1.demo2 demo1:继承Thread类,重写run()方法 package thread_test; public class ThreadDemo1 extends ...

  4. LVS解决高并发,大数据量

    http://www.360doc.com/content/14/0726/00/11962419_397102114.shtml LVS的全称Linux vitual system,是由目前阿里巴巴 ...

  5. 这或许是最详细的JUC多线程并发总结

    多线程进阶---JUC并发编程 完整代码传送门,见文章末尾 1.Lock锁(重点) 传统 Synchronizd package com.godfrey.demo01; /** * descripti ...

  6. sql语句对数据库表进行加锁和解锁

    锁是数据库中的一个非常重要的概念,它主要用于多用户环境下保证数据库完整性和一致性. 我们知道,多个用户能够同时操纵同一个数据库中的数据,会发生数据不一致现象.即如果没有锁定且多个用户同时访问一个数据库 ...

  7. redis加锁

    1. redis加锁分类 redis能用的的加锁命令分表是INCR.SETNX.SET2. 第一种锁命令INCR 这种加锁的思路是, key 不存在,那么 key 的值会先被初始化为 0 ,然后再执行 ...

  8. redis加锁的几种实现

    redis加锁的几种实现 2017/09/21 1. redis加锁分类 redis能用的的加锁命令分表是INCR.SETNX.SET 2. 第一种锁命令INCR 这种加锁的思路是, key 不存在, ...

  9. Java 使用Redis缓存工具的图文详细方法

    开始在 Java 中使用 Redis 前, 我们需要确保已经安装了 redis 服务及 Java redis 驱动,且你的机器上能正常使用 Java. (1)Java的安装配置可以参考我们的 Java ...

随机推荐

  1. 13.Vue.js 组件

    组件(Component)是 Vue.js 最强大的功能之一. 组件可以扩展 HTML 元素,封装可重用的代码. 组件系统让我们可以用独立可复用的小组件来构建大型应用,几乎任意类型的应用的界面都可以抽 ...

  2. 【C/C++】二维数组的传参的方法/二维字符数组的声明,使用,输入,传参

    [问题] 定义了一个子函数,传参的内容是一个二维数组 编译提示错误 因为多维数组作为形参传入时,必须声明除第一位维外的确定值,否则系统无法编译(算不出偏移地址) [二维数组的传参] 方法一:形参为二维 ...

  3. python实现skywalking的trace模块过滤和报警

    skywalking本身的报警功能,用起来视乎不是特别好用,目前想实现对skywalking的trace中的错误接口进行过滤并报警通知管理员和开发.所以自己就用python对skywalking做了二 ...

  4. Mysql-5.6 二进制多实例部署

    目录 一.简介 二.环境声明 三.程序部署 一.简介 MySQL多实例就是在一台机器上开启多个不同的服务端口(如:3306,3307),运行多个MySQL服务进程,通过不同的socket监听不同的服务 ...

  5. MySQL如何把varchar类型字段转换成int类型进行倒叙排序

    SELECT * FROM sheet2 t1 WHERE t1.`金额`+'0' ORDER BY t1.`金额` DESC;

  6. Svelte入门——Web Components实现跨框架组件复用(二)

    在上节中,我们一起了解了如何使用Svelte封装Web Component,从而实现在不同页面间使用电子表格组件. Svelte封装组件跨框架复用,带来的好处也十分明显: 1.使用框架开发,更容易维护 ...

  7. [BUUCTF]REVERSE——[FlareOn4]login

    [FlareOn4]login 附件 步骤: 是个网页,直接打开,查看网页源码 百度了几个函数 charCodeAt(0)是返回当前字符的Unicode 编码 String.fromCharCode返 ...

  8. LuoguP7714 「EZEC-10」排列排序 题解

    Content 给定一个 \(1\sim n\) 的一个排列 \(p\),你每次可以选择一个区间 \([l,r]\) 并花费 \(r-l+1\) 的代价将下标在这个区间内的所有数升序排序,求使得排列 ...

  9. CF20C Dijkstra? 题解

    Content 给定一张 \(n\) 个点 \(m\) 条边的无向图,请判断是否有一条可行的从 \(1\) 到 \(n\) 的路径,有的话输出长度最短的,没有的话输出 -1. 数据范围:\(2\leq ...

  10. 二、Uniapp+vue+腾讯IM+腾讯音视频开发仿微信的IM聊天APP,支持各类消息收发,音视频通话,附vue实现源码(已开源)-腾讯云后台配置TXIM

    项目文章索引 1.项目引言 2.腾讯云后台配置TXIM 3.配置项目并实现IM登录 4.会话好友列表的实现 5.聊天输入框的实现 6.聊天界面容器的实现 7.聊天消息项的实现 8.聊天输入框扩展面板的 ...