先定义一个枚举。
package com.study.security.common.annotation; /** * @Description: 读、写锁 类型 * @Auther: BacHe * @Date: 2019/9/17 09:39 */ public enum ReadWriteType { //读锁 READ_TYPE, //写锁 WRITE_TYPE }
自定义一个注解
package com.study.security.common.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * @Description: 读、写锁 注解 * @Auther: BacHe * @Date: 2019/9/17 09:41 */ //注解生命周期:运行时有有效 @Retention(RetentionPolicy.RUNTIME) //注解应用范围:修饰方法 @Target({ElementType.METHOD}) public @interface ReadWriteLock { String key() default "ReadWriteLock_"; //默认是:读锁。 ReadWriteType type() default ReadWriteType.READ_TYPE; }
写获取读写锁的工具类,根据key 不同,使用不同的读写锁。
package com.study.security.common.annotation; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.locks.ReentrantReadWriteLock; /** * @Description: 读写锁工具 * @Auther: BacHe * @Date: 2019/9/17 10:02 */ public class ReadWriteLockUtil { private static Map<String,ReentrantReadWriteLock> map = new ConcurrentHashMap(); //获取锁,并保存到map中 public static ReentrantReadWriteLock getLock(String key){ ReentrantReadWriteLock readWriteLock =map.get(key); if (null == readWriteLock) { synchronized (key) { readWriteLock = map.get(key); if (null == readWriteLock) { readWriteLock = new ReentrantReadWriteLock(); map.put(key, readWriteLock); } } } return readWriteLock; } //获取锁,可能拿不到锁 public static ReentrantReadWriteLock getLockOrNull(String key){ return map.get(key); } //从map 中删除锁。 public static void delLock(String key) { map.remove(key); } }
对注解进行AOP切面编程
package com.study.security.common.annotation; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.Signature; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.core.DefaultParameterNameDiscoverer; import org.springframework.expression.Expression; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.expression.spel.support.StandardEvaluationContext; import org.springframework.stereotype.Component; import java.lang.reflect.Method; import java.util.concurrent.locks.ReentrantReadWriteLock; /** * @Description: 读写锁 AOP 切面编程 * @Auther: BacHe * @Date: 2019/9/17 09:49 */ @Component @Aspect public class ReadWriteLockAop { @Around("@annotation(com.study.security.common.annotation.ReadWriteLock)") public void lock(ProceedingJoinPoint joinPoint) throws NoSuchMethodException { String key = "lock_"; //获取方法签名 MethodSignature signature = (MethodSignature)joinPoint.getSignature(); //从切入点,获取目标对象的字节码,的方法。参数:(方法名,方法的所有参数类型) Method method = joinPoint.getTarget().getClass().getMethod(signature.getName(), signature.getMethod().getParameterTypes()); //从方法获取注解。 ReadWriteLock annotation = method.getAnnotation(ReadWriteLock.class); //从注解,获取注解信息。'ReadWriteLockAop' + #key String keyEL = annotation.key(); ReadWriteType type = annotation.type(); //2. 创建 springEL表达式 解析器 SpelExpressionParser parser = new SpelExpressionParser(); // 解析器 获取指定表达式 'ReadWriteLockAop' + #key 的表达式对象 Expression expression = parser.parseExpression(keyEL); // 设置解析上下文 StandardEvaluationContext context = new StandardEvaluationContext(); //2.1 创建默认参数名 发现者 DefaultParameterNameDiscoverer discoverer = new DefaultParameterNameDiscoverer(); //2.2 获取方法中的所有参数名。 String[] parameterNames = discoverer.getParameterNames(method); //2.3 获取切点方法中的所有参数值。 Object[] args = joinPoint.getArgs(); for (int i = 0; i < parameterNames.length; i++) { // 把参数名,参数值,设置到解析器上下文 context.setVariable(parameterNames[i],args[i].toString()); } //表达式 匹配 解析上下文 中的内容 ,拿到key key = key + expression.getValue(context).toString(); if (type == ReadWriteType.READ_TYPE) { ReentrantReadWriteLock lock = ReadWriteLockUtil.getLockOrNull(key); //读锁。可能拿到null if (null != lock) { lock.readLock().lock(); } try { //执行切点方法。 joinPoint.proceed(); } catch (Throwable throwable) { throwable.printStackTrace(); } finally { if (null != lock) { //释放锁 lock.readLock().unlock(); } } } else { //写锁。一定能拿到锁,非null ReentrantReadWriteLock lock = ReadWriteLockUtil.getLock(key); lock.writeLock().lock(); try { //执行切点方法。 joinPoint.proceed(); } catch (Throwable throwable) { throwable.printStackTrace(); } finally { //删除锁 ReadWriteLockUtil.delLock(key); //释放锁 lock.writeLock().unlock(); } } } }
遇到一个面试题。修改数据库然后删除缓存的短暂时间内的数据不一致问题怎么解决?
我想用读写锁来进行控制。读缓存的时候,使用读锁或不使用锁。修改数据库&删除缓存的时候,创建读写锁,使用写锁,此时发生的读操作,就会使用读锁。
如此就能解决了修改数据和读取数据的数据不一致问题。
哪里考虑不周,欢迎各位大佬指正!
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
有些大佬说,我这个是单机版的,不适合分布式项目。其实把存储的本地map换成存到redis缓存中,不就支持分布式了吗。
package com.study.framework.annotation; import com.study.framework.cache.redis.RedisClient; import java.util.concurrent.locks.ReentrantReadWriteLock; /** * @Description: 读写锁工具 * @Auther: BacHe * @Date: 2019/9/17 10:02 */ public class ReadWriteLockUtil { //获取锁,并保存到redis缓存中 public static ReentrantReadWriteLock getLock(String key){ ReentrantReadWriteLock readWriteLock = RedisClient.getValue(key, ReentrantReadWriteLock.class); if (null == readWriteLock) { synchronized (key) { //双重检测 readWriteLock = RedisClient.getValue(key, ReentrantReadWriteLock.class); if (null == readWriteLock) { readWriteLock = new ReentrantReadWriteLock(); RedisClient.setValue(key,readWriteLock); } } } return readWriteLock; } //获取锁,可能拿不到锁 public static ReentrantReadWriteLock getLockOrNull(String key){ return RedisClient.getValue(key, ReentrantReadWriteLock.class); } //从map 中删除锁。 public static void delLock(String key) { RedisClient.delKey(key); } }
还有什么问题,请指正!