栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

简单注解实现集群同步锁(spring+redis+注解)

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

简单注解实现集群同步锁(spring+redis+注解)

互联网面试的时候,是不是面试官常问一个问题如何保证集群环境下数据操作并发问题,常用的synchronized肯定是无法满足了,或许你可以借助for update对数据加锁。本文的最终解决方式你只要在方法上加一个@P4jSyn注解就能保证集群环境下同synchronized的效果,且锁的key可以任意指定。本注解还支持了锁的超时机制。

本文需要对Redis、spring和spring-data-redis有一定的了解。当然你可以借助本文的思路对通过注解对方法返回数据进行缓存,类似com.google.code.simple-spring-memcached的@ReadThroughSingleCache。

第一步:  介绍两个自定义注解P4jSyn、P4jSynKey

P4jSyn:必选项,标记在方法上,表示需要对该方法加集群同步锁;

P4jSynKey:可选项,加在方法参数上,表示以方法某个参数作为锁的key,用来保证更多的坑,P4jSynKey并不是强制要添加的,当没有P4jSynKey标记的情况下只会以P4jSyn的synKey作为锁key。

package com.yaoguoyin.redis.lock; 
import java.lang.annotation.ElementType; 
import java.lang.annotation.Inherited; 
import java.lang.annotation.Retention; 
import java.lang.annotation.RetentionPolicy; 
import java.lang.annotation.Target; 
 
@Target({ ElementType.METHOD }) 
@Retention(RetentionPolicy.RUNTIME) 
@Inherited 
public @interface P4jSyn { 
  
 String synKey(); 
  
 long keepMills() default 20 * 1000; 
  
 boolean toWait() default true; 
  
 long sleepMills() default 10; 
  
 long maxSleepMills() default 60 * 1000; 
} 
package com.yaoguoyin.redis.lock; 
import java.lang.annotation.ElementType; 
import java.lang.annotation.Inherited; 
import java.lang.annotation.Retention; 
import java.lang.annotation.RetentionPolicy; 
import java.lang.annotation.Target; 
 
@Target({ ElementType.PARAMETER }) 
@Retention(RetentionPolicy.RUNTIME) 
@Inherited 
public @interface P4jSynKey { 
  
 int index() default 0; 
} 

这里就不再对两个注解进行使用上的解释了,因为注释已经说明的很详细了。

使用示例:

package com.yaoguoyin.redis.lock; 
import org.springframework.stereotype.Component; 
@Component 
public class SysTest { 
 private static int i = 0; 
 @P4jSyn(synKey = "12345") 
 public void add(@P4jSynKey(index = 1) String key, @P4jSynKey(index = 0) int key1) { 
 i++; 
 System.out.println("i=-===========" + i); 
 } 
} 

第二步:切面编程

在不影响原有代码的前提下,保证执行同步,目前最直接的方式就是使用切面编程

package com.yaoguoyin.redis.lock; 
import java.lang.annotation.Annotation; 
import java.lang.reflect.Method; 
import java.util.SortedMap; 
import java.util.TreeMap; 
import java.util.concurrent.TimeUnit; 
import java.util.concurrent.TimeoutException; 
import org.aspectj.lang.ProceedingJoinPoint; 
import org.aspectj.lang.annotation.Around; 
import org.aspectj.lang.annotation.Aspect; 
import org.aspectj.lang.reflect.MethodSignature; 
import org.springframework.beans.factory.annotation.Autowired; 
import org.springframework.beans.factory.annotation.Qualifier; 
import org.springframework.data.redis.core.BoundValueOperations; 
import org.springframework.data.redis.core.RedisTemplate; 
 
@Aspect 
public class RedisLockAspect { 
 @Autowired 
 @Qualifier("redisTemplate") 
 private RedisTemplate redisTemplate; 
 @Around("execution(* com.yaoguoyin..*(..)) && @annotation(com.yaoguoyin.redis.lock.P4jSyn)") 
 public Object lock(ProceedingJoinPoint pjp) throws Throwable { 
 P4jSyn lockInfo = getLockInfo(pjp); 
 if (lockInfo == null) { 
  throw new IllegalArgumentException("配置参数错误"); 
 } 
 String synKey = getSynKey(pjp, lockInfo.synKey()); 
 if (synKey == null || "".equals(synKey)) { 
  throw new IllegalArgumentException("配置参数synKey错误"); 
 } 
 boolean lock = false; 
 Object obj = null; 
 try { 
  // 超时时间 
  long maxSleepMills = System.currentTimeMillis() + lockInfo.maxSleepMills(); 
  while (!lock) { 
  long keepMills = System.currentTimeMillis() + lockInfo.keepMills(); 
  lock = setIfAbsent(synKey, keepMills); 
  // 得到锁,没有人加过相同的锁 
  if (lock) { 
   obj = pjp.proceed(); 
  } 
  // 锁设置了没有超时时间 
  else if (lockInfo.keepMills() <= 0) { 
   // 继续等待获取锁 
   if (lockInfo.toWait()) { 
   // 如果超过最大等待时间抛出异常 
   if (lockInfo.maxSleepMills() > 0 && System.currentTimeMillis() > maxSleepMills) { 
    throw new TimeoutException("获取锁资源等待超时"); 
   } 
   TimeUnit.MILLISECONDS.sleep(lockInfo.sleepMills()); 
   } else { 
   break; 
   } 
  } 
  // 已过期,并且getAndSet后旧的时间戳依然是过期的,可以认为获取到了锁 
  else if (System.currentTimeMillis() > getLock(synKey) && (System.currentTimeMillis() > getSet(synKey, keepMills))) { 
   lock = true; 
   obj = pjp.proceed(); 
  } 
  // 没有得到任何锁 
  else { 
   // 继续等待获取锁 
   if (lockInfo.toWait()) { 
   // 如果超过最大等待时间抛出异常 
   if (lockInfo.maxSleepMills() > 0 && System.currentTimeMillis() > maxSleepMills) { 
    throw new TimeoutException("获取锁资源等待超时"); 
   } 
   TimeUnit.MILLISECONDS.sleep(lockInfo.sleepMills()); 
   } 
   // 放弃等待 
   else { 
   break; 
   } 
  } 
  } 
 } catch (Exception e) { 
  e.printStackTrace(); 
  throw e; 
 } finally { 
  // 如果获取到了锁,释放锁 
  if (lock) { 
  releaseLock(synKey); 
  } 
 } 
 return obj; 
 } 
  
 private String getSynKey(ProceedingJoinPoint pjp, String synKey) { 
 try { 
  synKey = "RedisSyn+" + synKey; 
  Object[] args = pjp.getArgs(); 
  if (args != null && args.length > 0) { 
  MethodSignature methodSignature = (MethodSignature) pjp.getSignature(); 
  Annotation[][] paramAnnotationArrays = methodSignature.getMethod().getParameterAnnotations(); 
  SortedMap keys = new TreeMap(); 
 
  for (int ix = 0; ix < paramAnnotationArrays.length; ix++) { 
   P4jSynKey p4jSynKey = getAnnotation(P4jSynKey.class, paramAnnotationArrays[ix]); 
   if (p4jSynKey != null) { 
   Object arg = args[ix]; 
   if (arg != null) { 
    keys.put(p4jSynKey.index(), arg.toString()); 
   } 
   } 
  } 
  if (keys != null && keys.size() > 0) { 
   for (String key : keys.values()) { 
   synKey = synKey + key; 
   } 
  } 
  } 
  return synKey; 
 } catch (Exception e) { 
  e.printStackTrace(); 
 } 
 return null; 
 } 
 @SuppressWarnings("unchecked") 
 private static  T getAnnotation(final Class annotationClass, final Annotation[] annotations) { 
 if (annotations != null && annotations.length > 0) { 
  for (final Annotation annotation : annotations) { 
  if (annotationClass.equals(annotation.annotationType())) { 
   return (T) annotation; 
  } 
  } 
 } 
 return null; 
 } 
  
 private P4jSyn getLockInfo(ProceedingJoinPoint pjp) { 
 try { 
  MethodSignature methodSignature = (MethodSignature) pjp.getSignature(); 
  Method method = methodSignature.getMethod(); 
  P4jSyn lockInfo = method.getAnnotation(P4jSyn.class); 
  return lockInfo; 
 } catch (Exception e) { 
  e.printStackTrace(); 
 } 
 return null; 
 } 
 public BoundValueOperations getOperations(String key) { 
 return redisTemplate.boundValueOps(key); 
 } 
  
 public boolean setIfAbsent(String key, Long value) { 
 return getOperations(key).setIfAbsent(value); 
 } 
 public long getLock(String key) { 
 Long time = getOperations(key).get(); 
 if (time == null) { 
  return 0; 
 } 
 return time; 
 } 
 public long getSet(String key, Long value) { 
 Long time = getOperations(key).getAndSet(value); 
 if (time == null) { 
  return 0; 
 } 
 return time; 
 } 
 public void releaseLock(String key) { 
 redisTemplate.delete(key); 
 } 
} 

RedisLockAspect会对添加注解的方法进行特殊处理,具体可看lock方法。

大致思路就是:

1、首选借助redis本身支持对应的setIfAbsent方法,该方法的特点是如果redis中已有该数据不保存返回false,不存该数据保存返回true;

2、如果setIfAbsent返回true标识拿到同步锁,可进行操作,操作后并释放锁;

3、如果没有通过setIfAbsent拿到数据,判断是否对锁设置了超时机制,没有设置判断是否需要继续等待;

4、判断是否锁已经过期,需要对(System.currentTimeMillis() > getLock(synKey) && (System.currentTimeMillis() > getSet(synKey, keepMills)))进行细细的揣摩一下,getSet可能会改变了其他人拥有锁的超时时间,但是几乎可以忽略;

5、没有得到任何锁,判断继续等待还是退出。

第三步:spring的基本配置

#*****************jedis连接参数设置*********************# 
 
#redis服务器ip # 
redis.hostName=127.0.0.1 
 
#redis服务器端口号# 
redis.port=6379 
 
#redis服务器外部访问密码 
redis.password=XXXXXXXXXX 
 
#************************jedis池参数设置*******************# 
 
#jedis的最大分配对象# 
jedis.pool.maxActive=1000 
 
jedis.pool.minIdle=100 
 
#jedis最大保存idel状态对象数 # 
jedis.pool.maxIdle=1000 
 
#jedis池没有对象返回时,最大等待时间 # 
jedis.pool.maxWait=5000 
 
#jedis调用borrowObject方法时,是否进行有效检查# 
jedis.pool.testonBorrow=true 
 
#jedis调用returnObject方法时,是否进行有效检查 # 
jedis.pool.testonReturn=true 
 
 
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
 

redis的安装本文就不再说明。

测试

package com.yaoguoyin.redis; 
import org.junit.runner.RunWith; 
import org.springframework.test.context.ContextConfiguration; 
import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests; 
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 
@RunWith(SpringJUnit4ClassRunner.class) 
@ContextConfiguration(locations = { "classpath:meta-INF/spring/redis.xml" }) 
public class baseTest extends AbstractJUnit4SpringContextTests { 
} 
package com.yaoguoyin.redis.lock; 
import java.util.concurrent.TimeUnit; 
import org.junit.Test; 
import org.springframework.beans.factory.annotation.Autowired; 
import com.yaoguoyin.redis.baseTest; 
public class RedisTest extends baseTest { 
 @Autowired 
 private SysTest sysTest; 
 @Test 
 public void testHello() throws InterruptedException { 
 for (int i = 0; i < 100; i++) { 
  new Thread(new Runnable() { 
  @Override 
  public void run() { 
   try { 
   TimeUnit.SECONDS.sleep(1); 
   } catch (InterruptedException e) { 
   e.printStackTrace(); 
   } 
   sysTest.add("xxxxx", 111111); 
  } 
  }).start(); 
 } 
 TimeUnit.SECONDS.sleep(20); 
 } 
 @Test 
 public void testHello2() throws InterruptedException{ 
 sysTest.add("xxxxx", 111111); 
 TimeUnit.SECONDS.sleep(10); 
 } 
} 

你可以对

void com.yaoguoyin.redis.lock.SysTest.add(@P4jSynKey(index=1) String key, @P4jSynKey(index=0) int key1)

去除注解@P4jSyn进行测试对比。

ps:本demo的执行性能取决于redis和Java交互距离;成千山万单锁并发建议不要使用这种形式,直接通过redis等解决,本demo只解决小并发不想耦合代码的形式。

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,同时也希望多多支持考高分网!

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/147983.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号