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. 4、Linux下安装tomcat

    Linux系统下安装tomcat 一.安装JDK 安装Tomcat之前需要安装JDk,安装JDk请参考:JDK安装. 二.Linux安装Tomcat 1.官网上下载Tomcat       Apach ...

  2. Nginx 架构基础

    1 Nginx请求处理流程 2 Nginx进程结构 3 Nginx进程管理:信号 3.1 Master进程 监控worker进程 CHLD 管理worker进程 接收信号 TERM,INT QUIT ...

  3. Log4j2再发新版本2.16.0,完全删除Message Lookups的支持,加固漏洞防御

    昨天,Apache Log4j 团队再次发布了新版本:2.16.0! 2.16.0 更新内容 默认禁用JNDI的访问,用户需要通过配置log4j2.enableJndi参数开启 默认允许协议限制为:j ...

  4. [BUUCTF]PWN——others_babystack

    others_babystack 附件 步骤: 例行检查,64位程序,开了挺多保护 本地试运行一下程序 64位ida载入,看main函数 1是read函数,存在栈溢出:2是puts函数,3退出 利用思 ...

  5. 前置任务(Project)

    <Project2016 企业项目管理实践>张会斌 董方好 编著 在[前置任务列]中编辑任务关联,这是个正经的设置. 说他"正经",是因为在[手动模式]下,这个设置也是 ...

  6. 背水一战——CSP2021/NOIP2021 游记

    洛谷 version 转载本文章的其他链接: 1(S00021 提供) 2(Ew_Cors 提供) \[\texttt{2021.9.10} \] 终于开坑了. 笑死,初赛根本还没开始复习,反正初赛也 ...

  7. 将本地的react项目代码打包到服务器

    打包过程 连接服务器 ssh root@xx.xx.xx.xx(服务器IP) 定位到当前项目目录 cd /path/projectName 更新代码到服务器 git pull 执行打包命令(这里我创建 ...

  8. IDEA把Main方法打包成jar包

    创建一个maven项目 写一个main方法 Module:选择main方法所在的模块,我这里只有一个模块 所以默认选中 Main Class:选择main方法所在的类 Directory for ME ...

  9. vue-子组件创建/注册/使用流程

    流程分为三步 非单文件组件:(实际不用,因为很麻烦,框架都是多文件组件) 局部注册 1.创建一个组件 const school = Vue.extend({ // 传入配置对象 // 子组件配置对象不 ...

  10. 【LeetCode】面试题 17.16. 按摩师 解题报告(C++)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客:http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 动态规划 日期 题目地址:https://leetco ...