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. 莫烦python教程学习笔记——线性回归模型的属性

    #调用查看线性回归的几个属性 # Youtube video tutorial: https://www.youtube.com/channel/UCdyjiB5H8Pu7aDTNVXTTpcg # ...

  2. Redis cluster 集群报错合集

    目录 一.连接集群操作报错(error)MOVED 二.集群关闭后重启报错 三.Redis (error) NOAUTH Authentication required 四.Redis集群使用中突然挂 ...

  3. Jenkins多分支构建

    目录 一.创建多分支pipeline 二.根据分支部署 gitlab触发与多分支 Generic Webhook多分支 一.创建多分支pipeline 在实际中,需要多分支同时进行开发.如果每个分支都 ...

  4. pipeline groovy

    目录 一.变量 一.变量 1.直接定义 def x="abc" 2.从脚本执行结果赋值变量 branch = "/jen_script/return-branch.sh ...

  5. pipeline脚本管理

    目录 一.代码仓库 二.远程拉取 一.代码仓库 1.使用gitlab做pipeline脚本的存储,新建一个仓库 2.新建文件,把代码放进去 脚本名可以按照规律填写,环境_应用名_类型,例如:test_ ...

  6. 转:UITableView学习笔记

    UITableView学习笔记        作者:一片枫叶 看TableView的资料其实已经蛮久了,一直想写点儿东西,却总是因为各种原因拖延,今天晚上有时间静下心来记录一些最近学习的 TableV ...

  7. Spring Cloud Alibaba微服务架构入门最容易理解篇

    微服务架构介绍 Spring Cloud Alibaba推荐的微服务生态架构基于分层架构实现如下: 接入层:最外层为LVS+Keepalived,可承受几十万级高并发流量洪峰,然后再通过内层的ngin ...

  8. IPv6 DDNS 阿里云动态解析程序推荐: AliyunDdnsCSharp

    IPV6 DDNS 设置 概述 中国移动宽带提供了公网IPv6地址,为了物尽其用,于是折腾了域名到IPv6 的解析服务. 平台使用的阿里云解析DNS,平台提供了接口可以方便的添加与修改解析地址. 本打 ...

  9. nim_duilib(6)之listbox

    introduction 更多控件用法,请参考 here 和 源码. 本文的代码基于这里 本文将演示listbox的添加,删除,删除选中项,添加到指定位置等常用功能. xml文件添加代码 基于上一篇, ...

  10. 【九度OJ】题目1197:奇偶校验 解题报告

    [九度OJ]题目1197:奇偶校验 解题报告 标签(空格分隔): 九度OJ http://ac.jobdu.com/problem.php?pid=1197 题目描述: 输入一个字符串,然后对每个字符 ...