在企业的项目中,经常会碰到多线程安全的问题。特别是在涉及到金钱方面的,安全问题更是重中之重。如何保证多线程下的安全就成了必须要解决的问题。

在之前负责的某个项目中,有几个地方就被人恶意攻击过。用户申请提现的时候,通过接口快速访问,可以跳过钱包余额的校验达到多次提现。在微信小程序支付订单的时候,小程序支付完之后,瞬时调用多次检查订单状态的接口,也会导致多线程的问题,导致钱包余额增加多次。

最开始是用synchronized锁来解决这一问题的。不过synchronized锁意味的同一时刻该接口只能被一个人访问,在用户量很大的时候,有可能会造成用户等待时间过长,体验不好。所以使用了redis的分布式锁来解决高并发下的线程安全问题。

1.RedisLock.java

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RedisLock {
​/** 锁的资源,redis的key, */String value() default "default";/** 持锁时间,单位毫秒*/long keepMills() default 2000;/** 当获取失败时候动作*/LockFailAction action() default LockFailAction.CONTINUE;public enum LockFailAction{/** 放弃 */GIVEUP,/** 继续 */CONTINUE;}/** 重试的间隔时间,设置GIVEUP忽略此项*/long sleepMills() default 150;/** 重试次数*/int retryTimes() default 30;
}

2.RedisLockAspect.java

@Aspect
@Component
public class RedisLockAspect {public static Logger logger = LoggerFactory.getLogger(RedisLockAspect.class);@Pointcut("@annotation(xin.dayukeji.common.annotation.RedisLock)")public void point() {}@Autowiredprivate RedisLockService orderLockService;@Around(value = "point()")public Object around(ProceedingJoinPoint joinPoint) throws Throwable {Object[] args = joinPoint.getArgs();// 获取方法名称String methodName = joinPoint.getSignature().getName();// 获取所有参数类型Class<?>[] par = ((MethodSignature) joinPoint.getSignature()).getParameterTypes();// 获取目标类Class<?> classTarget = joinPoint.getTarget().getClass();// 获取目标方法Method methodTarget = classTarget.getMethod(methodName, par);RedisLock redisLock = methodTarget.getDeclaredAnnotation(RedisLock.class);if (redisLock != null) {String key = (String) AnnotationResolver.newInstance().resolver(joinPoint, redisLock.value());System.out.println(key);int retryTimes = redisLock.action().equals(RedisLock.LockFailAction.CONTINUE) ? redisLock.retryTimes() : 0;boolean lock = orderLockService.lock(key, redisLock.keepMills(), retryTimes, redisLock.sleepMills());if (!lock) {logger.info("get lock failed : " + key);return null;//获取锁失败,不执行逻辑}try {logger.debug("get lock success : " + key);//得到锁,执行方法return joinPoint.proceed();} finally {//释放锁boolean releaseLock = orderLockService.releaseLock(key);logger.error("release lock : " + key + (releaseLock ? " success" : " failed"));}}return null;}
}

3.RedisLockService.java

@Service
public class RedisLockService {@Autowiredprivate RedisTemplate<String,Object> template;@Autowiredprivate Env env;
​private static final long LOCK_EXPIRE = 600;
​
​/*** 锁操作,对某个key加锁,* @param key 需要加锁的key* @param keepTime 保持锁存在的时间(即多少时间后会失效),* @param retryTimes 线程没有取到锁需要重试去取锁,的重试次数* @param sleepTime 线程没有取到锁后,距离下次取锁操作的时间间隔。* @return*/public boolean lock(String key, long keepTime, int retryTimes, long sleepTime) {boolean lock = lock(key, keepTime);while (!lock && retryTimes-- > 0){//当没有取到锁,并且还有重试次数,继续取锁。取到锁或没有重试次数,跳出循环。try {Thread.sleep(sleepTime);lock = lock(key, keepTime);} catch (InterruptedException e) {e.printStackTrace();return false;}}return lock;}
​
​public boolean lock(String key){return lock(key, LOCK_EXPIRE);}
​/*** 加锁并设置失效时间* @param key 需要加锁的key* @param keepTime 保持锁存在的时间(即多少时间后会失效),* @return*/public boolean lock(String key, Long keepTime){String lock =  env.getProject() + ":" + key;return template.execute(new RedisCallback<Boolean>() {@Overridepublic Boolean doInRedis(RedisConnection connection) throws DataAccessException {//取当前时间戳,存入valuelong expire = System.currentTimeMillis();//取出redis原始操作JedisCommands commands = (JedisCommands) connection.getNativeConnection();/* NX: 表示只有当此key值不存在时才能存入成功* PX: 设置此key值的失效时间,单位为毫秒* 这样当key不存在时,第一个线程能取到锁,其他线程无法set key,取不到锁,* 等到第一个线程被释放或者锁过了失效时间,才可以取到锁*/String acquire = commands.set(lock, String.valueOf(expire + keepTime), "NX", "PX", keepTime);return "OK".equals(acquire);}});}
​/*** 当业务逻辑完成后,需要将锁释放。* @param key* @return*/public boolean releaseLock(String key){String lock = env.getProject() + ":" + key;Boolean delete = template.delete(lock);return delete==null?false:delete;}/***   用于测试的方法*/@RedisLock(value = "#{id}")public void testLock(String id) {for (int i = 1; i < 10; i++) {System.out.println(i);try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}}}
​
}

4.AnnotationResolver.java

package xin.dayukeji.common.util;import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.reflect.MethodSignature;import java.lang.reflect.Method;/*** 该类的作用可以把方法上的参数绑定到注解的变量中,注解的语法#{变量名}* 能解析类似#{task}或者#{task.taskName}或者{task.project.projectName}** @author liuxg* date 2016年4月13日 下午8:42:34*/
public class AnnotationResolver {private static AnnotationResolver resolver;public static AnnotationResolver newInstance() {if (resolver == null) {return resolver = new AnnotationResolver();} else {return resolver;}}/*** 解析注解上的值** @param joinPoint 就是joinPoint* @param str       需要解析的字符串* @return object*/public Object resolver(JoinPoint joinPoint, String str) {if (str == null) return null;Object value = null;if (str.matches(".*#\\{.*\\}.*")) {// 如果name匹配上了#{},则把内容当作变量String newStr = str.replaceAll(".*#\\{", "").replaceAll("\\}.*", "");if (newStr.contains(".")) { // 复杂类型try {value = complexResolver(joinPoint, newStr);} catch (Exception e) {e.printStackTrace();}} else {value = simpleResolver(joinPoint, newStr);}String[] split = str.split("#\\{.*\\}");switch (split.length) {case 1:value = split[0] + value;break;case 2:value = split[0] + value + split[1];break;default:}} else { //非变量value = str;}return value;}private Object complexResolver(JoinPoint joinPoint, String str) throws Exception {MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();String[] names = methodSignature.getParameterNames();Object[] args = joinPoint.getArgs();String[] strs = str.split("\\.");for (int i = 0; i < names.length; i++) {if (strs[0].equals(names[i])) {Object obj = args[i];Method dmethod = obj.getClass().getDeclaredMethod(getMethodName(strs[1]), null);Object value = dmethod.invoke(args[i]);return getValue(value, 1, strs);}}return null;}private Object getValue(Object obj, int index, String[] strs) {try {if (obj != null && index < strs.length - 1) {Method method = obj.getClass().getDeclaredMethod(getMethodName(strs[index + 1]), null);obj = method.invoke(obj);getValue(obj, index + 1, strs);}return obj;} catch (Exception e) {e.printStackTrace();return null;}}private String getMethodName(String name) {return "get" + name.replaceFirst(name.substring(0, 1), name.substring(0, 1).toUpperCase());}private Object simpleResolver(JoinPoint joinPoint, String str) {MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();String[] names = methodSignature.getParameterNames();Object[] args = joinPoint.getArgs();for (int i = 0; i < names.length; i++) {if (str.equals(names[i])) {return args[i];}}return null;}}

5.TestController.java

@RestController
@RequestMapping("/test")
public class TestController {@Autowiredprivate RedisLockService redisLockService;
​/*** 获取分布式redis*/@GetMapping("/redisLock")@ResponseBody@ExcludeInterceptorpublic Report testRedisLock() {for (int i = 0; i < 10; i++) {new Thread(new Runnable() {@Overridepublic void run() {redisLockService.testLock("test Lock");}}).start();}return ReportFactory.S_0_OK.report();}
}

启动项目,调用/test/redisLock接口测试该注解,控制台打印

test Lock
test Lock
test Lock
test Lock
test Lock
test Lock
test Lock
test Lock
1
2
3
4
5
6
7
8
9
2019-09-04 19:03:29.864 ERROR 5313 --- [      Thread-46] x.d.common.aspect.RedisLockAspect        : release lock : test Lock success
test Lock
1
2
3
4
5
6
7
8
9
2019-09-04 19:03:29.993 ERROR 5313 --- [      Thread-53] x.d.common.aspect.RedisLockAspect        : release lock : test Lock success
test Lock
...
...
...

在SpringBoot中使用redis实现分布式锁相关推荐

  1. java中使用Redis实现分布式锁

    前言 目前很多大型的互联网公司后端都采用了分布式架构来支撑前端应用,其中服务拆分就是分布式的一种体现,既然服务拆分了,那么多个服务协调工作就会出现一些资源竞争的情况.比如多个服务对同一个表中的数据进行 ...

  2. 实际开发中使用Redis做分布式锁,躲坑指南,收藏起来

    今天我们来聊聊Redis分布式锁,曾经被Redis分布式锁的坑给坑惨了,接下来,我就进行一个完整的整理,希望大家都能避免踩坑. 在分布式系统中,由于redis分布式锁相对于更简单和高效,成为了分布式锁 ...

  3. 用 Redis 实现分布式锁(Java 版)

    用 Redis 实现分布式锁(Java 版) 核心代码 完整代码   分布式锁是一种解决分布式临界资源并发读写的一种技术.本文详细介绍了在 Java 中使用 Redis 实现分布式锁的方法.为了方便, ...

  4. 使用Redis创建分布式锁

    点击上方蓝色字关注我们~ 在本文中,我们将讨论如何在.NET Core中使用Redis创建分布式锁. 当我们构建分布式系统时,我们将面临多个进程一起处理共享资源,由于其中只有一个可以一次使用共享资源, ...

  5. springboot整合redis实现分布式锁思想

    思路 所有响应获取锁的线程都先尝试往redis中创建一个缓存数据,所有线程的key必须相同.使用的是redis的setnx命令.就只有一个线程能够创建成功,创建成功的线程就成功获取锁. 没有获取锁的线 ...

  6. js 拉勾网效果_Node.js 中实践基于 Redis 的分布式锁实现

    在一些分布式环境下.多线程并发编程中,如果对同一资源进行读写操作,避免不了的一个就是资源竞争问题,通过引入分布式锁这一概念,可以解决数据一致性问题. 作者简介:五月君,Nodejs Developer ...

  7. 【面试题】Redis中是如何实现分布式锁的

    分布式锁常见的三种实现方式: 数据库乐观锁: 基于Redis的分布式锁: 基于ZooKeeper的分布式锁. Redis的分布式锁 Redis要实现分布式锁,以下条件应该得到满足 互斥性:在任意时刻, ...

  8. Java基于redis实现分布式锁(SpringBoot)

    前言 分布式锁,其实原理是就是多台机器,去争抢一个资源,谁争抢成功,那么谁就持有了这把锁,然后去执行后续的业务逻辑,执行完毕后,把锁释放掉. 可以通过多种途径实现分布式锁,例如利用数据库(mysql等 ...

  9. redis实现轮询算法_【07期】Redis中是如何实现分布式锁的?

    点击上方"Java面试题精选",关注公众号 面试刷图,查缺补漏 分布式锁常见的三种实现方式: 数据库乐观锁: 基于Redis的分布式锁: 基于ZooKeeper的分布式锁. 本地面 ...

最新文章

  1. ZooKeeper私人学习笔记
  2. 捉虫记 NullPointerException
  3. VCL界面控件DevExpress VCL Controls发布v18.2.5|附下载
  4. mac地址厂商对应表_网络工程师一分钟搞懂MAC地址表知识点全部内容,建议收藏...
  5. SQL Server 2005中的分区表
  6. Filter handling in SAP gateway
  7. EntityFramework Core表名原理解析,让我来,揭开你神秘的面纱
  8. ajax 赋值 获取,ajax得到的数据赋值给js中的全局变量
  9. Ffmpeg快速应用开发
  10. meta标签的另一个用法
  11. python 读取元组对的key_Python基本认识基本类型
  12. mysql中多个left join子查询写法以及别名用法
  13. 协作中继认知无线电功率分配
  14. Nuxt.js重定向路由方式
  15. Neo4j 构建简单农业知识图谱(Agriculture KnowledgeGraph)
  16. bat 命令如何启动远程PC上的一个程序?
  17. HTML+CSS+JavaScript+Ajax+ECharts实现疫情实时监控大屏-2设计与实现
  18. MIMO-OFDM无线通信技术及MATLAB实现(3)MIMO信道模型
  19. java正态分布随机数_正态分布的随机数
  20. 区块链:信仰亦需理性

热门文章

  1. [思考]-ARM LR寄存器的思考
  2. IMAP和POP3的相关知识与区别
  3. 用户层CS段描述符信息
  4. nedmalloc结构分析
  5. C++约瑟夫问题求解
  6. Fidder监控请求响应时间(毫秒)和请求IP
  7. Thymeleaf选择器引用公共片段
  8. Java之Character类
  9. 二维字符数组按长度排序_字符串长度 字符数组长度
  10. 平板电脑什么牌子好点_什么平板电脑充电柜好?