本次基于注解+AOP实现分布式锁(招式与前文基于注解切换多数据源相同),话不多说,直接上样例:

首先自定义注解:设计时需要考虑锁的一般属性:keys,最大等待时间,超时时间,时间单位。

  1. package com.paic.phssp.springtest.redisson;
  2.  
  3. import java.lang.annotation.ElementType;
  4. import java.lang.annotation.Retention;
  5. import java.lang.annotation.RetentionPolicy;
  6. import java.lang.annotation.Target;
  7. import java.util.concurrent.TimeUnit;
  8.  
  9. @Retention(RetentionPolicy.RUNTIME)
  10. @Target(ElementType.METHOD)
  11. public @interface RequestLockable {
  12. String[] key() default "";
  13.  
  14. long maximumWaiteTime() default 2000;
  15.  
  16. long expirationTime() default 1000;
  17.  
  18. TimeUnit timeUnit() default TimeUnit.MILLISECONDS;
  19. }

新建一个抽象请求拦截器,设计模式:装饰模式,父类决定整体流程,具体细节交给字类实现,便于解耦扩展。

  1. package com.paic.phssp.springtest.redisson;
  2.  
  3. import org.apache.commons.lang3.StringUtils;
  4. import org.aspectj.lang.ProceedingJoinPoint;
  5. import org.aspectj.lang.Signature;
  6. import org.aspectj.lang.annotation.Around;
  7. import org.aspectj.lang.reflect.MethodSignature;
  8. import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
  9. import org.springframework.expression.EvaluationContext;
  10. import org.springframework.expression.Expression;
  11. import org.springframework.expression.ExpressionParser;
  12. import org.springframework.expression.common.TemplateParserContext;
  13. import org.springframework.expression.spel.standard.SpelExpressionParser;
  14. import org.springframework.expression.spel.support.StandardEvaluationContext;
  15.  
  16. import java.lang.reflect.Method;
  17. import java.util.Arrays;
  18. import java.util.HashMap;
  19. import java.util.Map;
  20. import java.util.Objects;
  21. import java.util.concurrent.TimeUnit;
  22. import java.util.concurrent.locks.Lock;
  23. import java.util.stream.Collectors;
  24.  
  25. public abstract class AbstractRequestLockInterceptor {
  26. protected abstract Lock getLock(String key);
  27.  
  28. protected abstract boolean tryLock(long waitTime, long leaseTime, TimeUnit unit, Lock lock) throws InterruptedException;
  29.  
  30. private static final String[] removeSigs = new String[]{"#","{","}"};
  31.  
  32. @Around("@annotation(RequestLockable)")
  33. public Object doAround(ProceedingJoinPoint point) throws Throwable {
  34. //获取连接点的方法签名对象
  35. Signature signature = point.getSignature();
  36. MethodSignature methodSignature = (MethodSignature) signature;
  37.  
  38. Method method = methodSignature.getMethod();
  39. String methodName = signature.getName();
  40.  
  41. //获取连接点所在的目标对象
  42. String targetName = point.getTarget().getClass().getName();
  43.  
  44. //获取连接点方法运行时的入参列表
  45. Object[] arguments = point.getArgs();
  46.  
  47. if (method != null && method.isAnnotationPresent(RequestLockable.class)) {
  48. RequestLockable requestLockable = method.getAnnotation(RequestLockable.class);
  49.  
  50. String requestLockKey = getLockBySpellKey(method, targetName, methodName, requestLockable.key(), arguments);
  51.  
  52. System.out.println(">>>>requestLockKey="+requestLockKey);
  53.  
  54. Lock lock = this.getLock(requestLockKey);
  55.  
  56. boolean isLock = this.tryLock(requestLockable.maximumWaiteTime(), requestLockable.expirationTime(),
  57. requestLockable.timeUnit(), lock);
  58. if (isLock) {
  59. try {
  60. return point.proceed();
  61. } finally {
  62. //释放锁资源
  63. lock.unlock();
  64. }
  65. } else {
  66. throw new RuntimeException("获取锁资源失败");
  67. }
  68. }
  69.  
  70. //通过反射执行目标对象的连接点处的方法
  71. return point.proceed();
  72. }
  73.  
  74. /**
  75. * 组装lock key
  76. *
  77. * @param method
  78. * @param targetName 对象名
  79. * @param methodName 方法名
  80. * @param keys 注解key
  81. * @param arguments 方法参数
  82. * @return
  83. */
  84. private String getLockBySpellKey(Method method, String targetName, String methodName, String[] keys, Object[] arguments) {
  85. StringBuilder lockKey = new StringBuilder();
  86. lockKey.append("lock.").append(targetName).append(".").append(methodName);
  87.  
  88. if (keys != null) {
  89. //Joiner Guava包
  90. //String keyStr = Joiner.on(".").skipNulls().join(keys);
  91. String keyStr = Arrays.stream(keys).filter(Objects::nonNull).collect(Collectors.joining("."));
  92. System.out.println("RequestLockable:keys="+keyStr);
  93.  
  94. if (!StringUtils.isBlank(keyStr)) {
  95. LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();
  96. String[] parameters = discoverer.getParameterNames(method);
  97.  
  98. //用Spell方法容易出问题,所以这里加了一些限制
  99. keyStr = creatSpellExpressionStr(keyStr,parameters,removeSigs);
  100.  
  101. int length = parameters.length;
  102. if(length > 0){
  103. if(!hasParameters(keyStr,parameters)){
  104. //不包含参数直接用keyStr
  105. lockKey.append("#").append(keyStr);
  106. }else{
  107. //keyStr 是否包含参数名,如果包含可用Spell表达式
  108. //用Spell方法容易出问题,所以这里加工下
  109. keyStr = creatSpellExpressionStr(keyStr,parameters,removeSigs);
  110. ExpressionParser parser = new SpelExpressionParser();
  111. EvaluationContext context = new StandardEvaluationContext();
  112. Map<String,Object> vMap = new HashMap<String,Object>();
  113. for (int i = 0; i < length; i++) {
  114. //key:方法参数名,val:值
  115. vMap.put(parameters[i],arguments[i]);
  116. }
  117. ((StandardEvaluationContext) context).setVariables(vMap);
  118.  
  119. //eg:#{#proKey}.asd#{#proKey}fa.#{#proId}#{#proKey} -> product.asdproductfa.123product
  120. Expression expression = parser.parseExpression(keyStr,new TemplateParserContext());
  121. String keysValue = expression.getValue(context, String.class);
  122. lockKey.append("#").append(keysValue);
  123. }
  124. }
  125. }
  126. }
  127. return lockKey.toString();
  128. }
  129.  
  130. private boolean hasParameters(String lockKey,String[] parameters){
  131. boolean hasFlag = false;
  132. for(String str : parameters){
  133. if(StringUtils.indexOf(lockKey,str) != -1){
  134. hasFlag = true;
  135. break;
  136. }
  137. }
  138. return hasFlag;
  139. }
  140.  
  141. private String creatSpellExpressionStr(String lockKey,String[] parameters,String[] removeSigs){
  142. //去掉#{}等字符
  143. for(String sig : removeSigs){
  144. lockKey = StringUtils.replace(lockKey,sig,"");
  145. }
  146.  
  147. for(String str : parameters){
  148. String repStr = "#{#"+str+"}";
  149. lockKey = StringUtils.replace(lockKey,str,repStr);
  150. }
  151. return lockKey;
  152. }
  153.  
  154. }

具体实现拦截类:

  1. package com.paic.phssp.springtest.redisson;
  2.  
  3. import org.aspectj.lang.annotation.Aspect;
  4. import org.redisson.Redisson;
  5. import org.redisson.api.RLock;
  6. import org.springframework.stereotype.Component;
  7.  
  8. import javax.annotation.Resource;
  9. import java.util.concurrent.TimeUnit;
  10. import java.util.concurrent.locks.Lock;
  11.  
  12. @Aspect
  13. @Component
  14. public class RedisRequestLockInterceptor extends AbstractRequestLockInterceptor {
  15. @Resource
  16. private Redisson redisson;
  17.  
  18. @Override
  19. protected Lock getLock(String key) {
  20. return redisson.getLock(key);
  21. }
  22.  
  23. @Override
  24. protected boolean tryLock(long waitTime, long leaseTime, TimeUnit unit, Lock lock) throws InterruptedException {
  25. return ((RLock) lock).tryLock(waitTime, leaseTime, unit);
  26. }
  27. }
  1. ProductService.java
  1. /**
  2. * 注意:key={"#proKey","#proId"} 与参数名一致时走Spell表达式
  3. * @param proKey
  4. * @param proId
  5. */
  6. @RequestLockable(key={"#{#proKey}","#{#proId}"}) //细化到参数,参数值不同时,不共享一个锁,相同时才会共享,这点要注意
  7. //@RequestLockable(key={"#lock_key_prod"})
  8. public void anotationProd(String proKey,int proId){
  9. String productId = stringRedisTemplate.opsForValue().get(proKey);
  10. int sprodId = Integer.parseInt(productId);
  11. if (sprodId > 0) {
  12. stringRedisTemplate.opsForValue().set("product", --sprodId + "");
  13. System.out.println(">>>>>>"+Thread.currentThread().getName() + ",product:" + sprodId + "");
  14. }
  15. try {
  16. Thread.sleep(100);
  17. } catch (InterruptedException e) {
  18. e.printStackTrace();
  19. }
  20. }

单元测试(比较懒,就不另外起工程测了,开多线程...)

  1. @Test
  2. public void testAnotationProd(){
  3. //开启2个线程
  4. Thread thread1 = new Thread(()-> productService.anotationProd("product",1));
  5. Thread thread2 = new Thread(()-> productService.anotationProd("product",1));
  6.  
  7. thread1.start();
  8. try {
  9. Thread.sleep(50);
  10. } catch (InterruptedException e) {
  11. e.printStackTrace();
  12. }
  13. thread2.start();
  14.  
  15. try {
  16. Thread.sleep(1000);
  17. } catch (InterruptedException e) {
  18. e.printStackTrace();
  19. }
  20. }

场景1:方法加注释:@RequestLockable(key={"#{#proKey}","#{#proId}"})

RequestLockable:keys=#{#proKey}.#{#proId}
>>>>requestLockKey=lock.com.paic.phssp.springtest.redisson.ProductService.anotationProd#product.1
RequestLockable:keys=#{#proKey}.#{#proId}
>>>>requestLockKey=lock.com.paic.phssp.springtest.redisson.ProductService.anotationProd#product.1
>>>>>>Thread-10,product:92
>>>>>>Thread-9,product:91

场景2:方法加注释:@RequestLockable(key={"#lock_key_prod"})
RequestLockable:keys=#lock_key_prod
>>>>requestLockKey=lock.com.paic.phssp.springtest.redisson.ProductService.anotationProd#lock_key_prod
RequestLockable:keys=#lock_key_prod
>>>>requestLockKey=lock.com.paic.phssp.springtest.redisson.ProductService.anotationProd#lock_key_prod
>>>>>>Thread-9,product:90
>>>>>>Thread-10,product:89

场景3:方法不加注释,此时两个线程同时去读写了
>>>>>>Thread-9,product:88
>>>>>>Thread-10,product:88

场景4:测试另一个线程,参数proId=2,发现两个线程同时去读写了
RequestLockable:keys=#{#proKey}.#{#proId}
>>>>requestLockKey=lock.com.paic.phssp.springtest.redisson.ProductService.anotationProd#product.1
RequestLockable:keys=#{#proKey}.#{#proId}
>>>>requestLockKey=lock.com.paic.phssp.springtest.redisson.ProductService.anotationProd#product.2
>>>>>>Thread-9,product:87
>>>>>>Thread-10,product:87

参考:

https://blog.csdn.net/qq_15427331/article/details/54630999

Redisson实现分布式锁(二)的更多相关文章

  1. Redisson实现分布式锁(3)—项目落地实现

    Redisson实现分布式锁(3)-项目落地实现 有关Redisson实现分布式锁前面写了两篇博客作为该项目落地的铺垫. 1.Redisson实现分布式锁(1)---原理 2.Redisson实现分布 ...

  2. Redisson实现分布式锁(2)—RedissonLock

    Redisson实现分布式锁(2)-RedissonLock 有关Redisson实现分布式锁上一篇博客讲了分布式的锁原理:Redisson实现分布式锁---原理 这篇主要讲RedissonLock和 ...

  3. Redisson实现分布式锁(1)---原理

    Redisson实现分布式锁(1)---原理 有关Redisson作为实现分布式锁,总的分3大模块来讲. 1.Redisson实现分布式锁原理 2.Redisson实现分布式锁的源码解析 3.Redi ...

  4. spring boot:用redis+redisson实现分布式锁(redisson3.11.1/spring boot 2.2)

    一,为什么要使用分布式锁? 如果在并发时锁定代码的执行,java中用synchronized锁保证了线程的原子性和可见性 但java锁只在单机上有效,如果是多台服务器上的并发访问,则需要使用分布式锁, ...

  5. 基于Redisson实现分布式锁源码解读

    文章目录 一.分布式锁的概念 和 使用场景 二.将redis官网对于分布式锁(红锁)的定义和Redisson实现做概括性总结 三.基于Redisson的分布式实现方案 四.加锁过程分析 五.锁重入过程 ...

  6. redisson之分布式锁实现原理(三)

    官网:https://github.com/redisson/redisson/wiki/%E7%9B%AE%E5%BD%95 一.什么是分布式锁 1.1.什么是分布式锁 分布式锁,即分布式系统中的锁 ...

  7. Redisson实现分布式锁

    转: Redisson实现分布式锁 Redisson文档参考:https://github.com/redisson/redisson/wiki/%E7%9B%AE%E5%BD%95 redis是实现 ...

  8. 使用Redisson实现分布式锁,Spring AOP简化之

    源码 Redisson概述 Redisson是一个在Redis的基础上实现的Java驻内存数据网格(In-Memory Data Grid).它不仅提供了一系列的分布式的Java常用对象,还提供了许多 ...

  9. 利用Redisson实现分布式锁及其底层原理解析

    Redis介绍 参考地址:https://blog.csdn.net/turbo_zone/article/details/83422215 redis是一个key-value存储系统.和Memcac ...

  10. 【高并发】你知道吗?大家都在使用Redisson实现分布式锁了!!

    写在前面 忘记之前在哪个群里有朋友在问:有出分布式锁的文章吗-@冰河?我的回答是:这周会有,也是[高并发]专题的.想了想,还是先发一个如何使用Redisson实现分布式锁的文章吧?为啥?因为使用Red ...

随机推荐

  1. 闭包引起的onclick不起作用

    问题描述:在html页面绑定onclick="cli()" 方法,定义在$(function( function cli(){} ))不起作用 $(function(){ func ...

  2. vue中如何使用echarts

    在vue中使用echarts主要是注意如何与vue生命周期相结合,从而做到数据驱动视图刷新 主要是以下几步: echarts的option配置项放在在data(){}或者computed(){}中 在 ...

  3. shell脚本之 给PNG图片添加后缀@3x

    1,给png图片加上后缀@3x #!/bin/sh #root_src=$(dirname $(PWD)) #echo ${root_src} image_path=${root_src}/image ...

  4. CentOS6.8下安装Redis

    1.由于Redis是使用C语言开发的,安装时需要对Redis的源码进行编译,编译依赖gcc环境,如果没有gcc,需要先安装gcc: yum install gcc-c++ 2.安装完成后,进入Redi ...

  5. repo forall -c 用法【转】

    本文转载自:https://blog.csdn.net/u010164190/article/details/78332484 .repo forall命令 # repo forall -help # ...

  6. HihoCoder 1636 Pangu and Stones(区间DP)题解

    题意:合并石子,每次只能合并l~r堆成1堆,代价是新石堆石子个数,问最后能不能合成1堆,不能输出0,能输出最小代价 思路:dp[l][r][t]表示把l到r的石堆合并成t需要的最小代价. 当t == ...

  7. SCU 4445 Right turn(dfs)题解

    思路:离散化之后,直接模拟就行,标记vis开三维 代码: #include<iostream> #include<algorithm> #include<cstdio&g ...

  8. Sql 通过表名查找所有列名

    SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'ImmediacyOutKu'

  9. P3239 [HNOI2015]亚瑟王

    思路 神仙概率dp 由于期望的线性性质,能够想到最后要求的期望价值就是把每个卡牌发动的概率\(g_i\)乘上伤害\(val_i\)之后加到一起 然后怎么求\(g_i\)呢,肯定是要dp的 我想了例如d ...

  10. cas4.2.4 登添加验证码

    看了很多添加验证码的博文,唯独没有4.24的 重点看第3条,其余的和别人博文大致相同 1.首先在cas工程的web.xml增加验证码功能的支持 <!-- 验证码功能 -->      &l ...