Java分布式锁的三种实现方案
|
方案一:数据库乐观锁 乐观锁通常实现基于数据版本(version)的记录机制实现的,比如有一张红包表(t_bonus),有一个字段(left_count)记录礼物的剩余个数,用户每领取一个奖品,对应的left_count减1,在并发的情况下如何要保证left_count不为负数,乐观锁的实现方式为在红包表上添加一个版本号字段(version),默认为0。 异常实现流程 -- 可能会发生的异常情况 -- 线程1查询,当前left_count为1,则有记录 select * from t_bonus where id = 10001 and left_count > 0 -- 线程2查询,当前left_count为1,也有记录 select * from t_bonus where id = 10001 and left_count > 0 -- 线程1完成领取记录,修改left_count为0,update t_bonus set left_count = left_count - 1 where id = 10001 -- 线程2完成领取记录,修改left_count为-1,产生脏数据 update t_bonus set left_count = left_count - 1 where id = 10001 通过乐观锁实现 -- 添加版本号控制字段 ALTER TABLE table ADD COLUMN version INT DEFAULT '0' NOT NULL AFTER t_bonus; -- 线程1查询,当前left_count为1,则有记录,当前版本号为1234 select left_count,version from t_bonus where id = 10001 and left_count > 0 -- 线程2查询,当前left_count为1,有记录,当前版本号为1234 select left_count,version from t_bonus where id = 10001 and left_count > 0 -- 线程1,更新完成后当前的version为1235,update状态为1,更新成功 update t_bonus set version = 1235,left_count = left_count-1 where id = 10001 and version = 1234 -- 线程2,更新由于当前的version为1235,udpate状态为0,更新失败,再针对相关业务做异常处理 update t_bonus set version = 1235,left_count = left_count-1 where id = 10001 and version = 1234 方案二:基于Redis的分布式锁 SETNX命令(SET if Not eXists) 第一种:使用redis的setnx()、expire()方法,用于分布式锁
这个方案其实是可以解决日常工作中的需求的,但从技术方案的探讨上来说,可能还有一些可以完善的地方。比如,如果在第一步setnx执行成功后,在expire()命令执行成功前,发生了宕机的现象,那么就依然会出现死锁的问题 第二种:使用redis的setnx()、get()、getset()方法,用于分布式锁,解决死锁问题
import cn.com.tpig.cache.redis.RedisService;
import cn.com.tpig.utils.SpringUtils;
/**
* Created by IDEA
* User: shma1664
* Date: 2016-08-16 14:01
* Desc: redis分布式锁
*/
public final class RedisLockUtil {
private static final int defaultExpire = 60;
private RedisLockUtil() {
//
}
/**
* 加锁
* @param key redis key
* @param expire 过期时间,单位秒
* @return true:加锁成功,false,加锁失败
*/
public static boolean lock(String key,int expire) {
RedisService redisService = SpringUtils.getBean(RedisService.class);
long status = redisService.setnx(key,"1");
if(status == 1) {
redisService.expire(key,expire);
return true;
}
return false;
}
public static boolean lock(String key) {
return lock2(key,defaultExpire);
}
/**
* 加锁
* @param key redis key
* @param expire 过期时间,单位秒
* @return true:加锁成功,false,加锁失败
*/
public static boolean lock2(String key,int expire) {
RedisService redisService = SpringUtils.getBean(RedisService.class);
long value = System.currentTimeMillis() + expire;
long status = redisService.setnx(key,String.valueOf(value));
if(status == 1) {
return true;
}
long oldExpireTime = Long.parseLong(redisService.get(key,"0"));
if(oldExpireTime < System.currentTimeMillis()) {
//超时
long newExpireTime = System.currentTimeMillis() + expire;
long currentExpireTime = Long.parseLong(redisService.getSet(key,String.valueOf(newExpireTime)));
if(currentExpireTime == oldExpireTime) {
return true;
}
}
return false;
}
public static void unLock1(String key) {
RedisService redisService = SpringUtils.getBean(RedisService.class);
redisService.del(key);
}
public static void unLock2(String key) {
RedisService redisService = SpringUtils.getBean(RedisService.class);
long oldExpireTime = Long.parseLong(redisService.get(key,"0"));
if(oldExpireTime > System.currentTimeMillis()) {
redisService.del(key);
}
}
}
public void drawRedPacket(long userId) {
String key = "draw.redpacket.userid:" + userId;
boolean lock = RedisLockUtil.lock2(key,60);
if(lock) {
try {
//领取操作
} finally {
//释放锁
RedisLockUtil.unLock(key);
}
} else {
new RuntimeException("重复领取奖励");
}
}
Spring AOP基于注解方式和SpEL实现开箱即用的redis分布式锁策略
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* RUNTIME
* 定义注解
* 编译器将把注释记录在类文件中,在运行时 VM 将保留注释,因此可以反射性地读取。
* @author shma1664
*
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RedisLockable {
String[] key() default "";
long expiration() default 60;
}
import javax.annotation.Resource;
import java.lang.reflect.Method;
import com.autohome.api.dealer.util.cache.RedisClient;
import com.google.common.base.Joiner;
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.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
/**
* Created by IDEA
* User: mashaohua
* Date: 2016-09-28 18:08
* Desc:
*/
@Aspect
@Component
public class RedisLockAop {
@Resource
private RedisClient redisClient;
@Pointcut("execution(* com.autohome.api.dealer.tuan.service.*.*(..))")
public void pointcut(){}
@Around("pointcut()")
public Object doAround(ProceedingJoinPoint point) throws Throwable{
Signature signature = point.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
Method method = methodSignature.getMethod();
String targetName = point.getTarget().getClass().getName();
String methodName = point.getSignature().getName();
Object[] arguments = point.getArgs();
if (method != null && method.isAnnotationPresent(RedisLockable.class)) {
RedisLockable redisLock = method.getAnnotation(RedisLockable.class);
long expire = redisLock.expiration();
String redisKey = getLockKey(targetName,methodName,redisLock.key(),arguments);
boolean isLock = RedisLockUtil.lock2(redisKey,expire);
if(!isLock) {
try {
return point.proceed();
} finally {
unLock2(redisKey);
}
} else {
throw new RuntimeException("您的操作太频繁,请稍后再试");
}
}
return point.proceed();
}
private String getLockKey(String targetName,String methodName,String[] keys,Object[] arguments) {
StringBuilder sb = new StringBuilder();
sb.append("lock.").append(targetName).append(".").append(methodName);
if(keys != null) {
String keyStr = Joiner.on(".").skipNulls().join(keys);
String[] parameters = ReflectParamNames.getNames(targetName,methodName);
ExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression(keyStr);
EvaluationContext context = new StandardEvaluationContext();
int length = parameters.length;
if (length > 0) {
for (int i = 0; i < length; i++) {
context.setVariable(parameters[i],arguments[i]);
}
}
String keysValue = expression.getValue(context,String.class);
sb.append("#").append(keysValue);
}
return sb.toString();
}
<!-- https://mvnrepository.com/artifact/javassist/javassist --> <dependency> <groupId>org.javassist</groupId> <artifactId>javassist</artifactId> <version>3.18.1-GA</version> </dependency>
import javassist.*;
import javassist.bytecode.CodeAttribute;
import javassist.bytecode.LocalVariableAttribute;
import javassist.bytecode.MethodInfo;
import org.apache.log4j.Logger;
/**
* Created by IDEA
* User: mashaohua
* Date: 2016-09-28 18:39
* Desc:
*/
public class ReflectParamNames {
private static Logger log = Logger.getLogger(ReflectParamNames.class);
private static ClassPool pool = ClassPool.getDefault();
static{
ClassClassPath classPath = new ClassClassPath(ReflectParamNames.class);
pool.insertClassPath(classPath);
}
public static String[] getNames(String className,String methodName) {
CtClass cc = null;
try {
cc = pool.get(className);
CtMethod cm = cc.getDeclaredMethod(methodName);
// 使用javaassist的反射方法获取方法的参数名
MethodInfo methodInfo = cm.getMethodInfo();
CodeAttribute codeAttribute = methodInfo.getCodeAttribute();
LocalVariableAttribute attr = (LocalVariableAttribute) codeAttribute.getAttribute(LocalVariableAttribute.tag);
if (attr == null) return new String[0];
int begin = 0;
String[] paramNames = new String[cm.getParameterTypes().length];
int count = 0;
int pos = Modifier.isStatic(cm.getModifiers()) ? 0 : 1;
for (int i = 0; i < attr.tableLength(); i++){
// 为什么 加这个判断,发现在windows 跟linux执行时,参数顺序不一致,通过观察,实际的参数是从this后面开始的
if (attr.variableName(i).equals("this")){
begin = i;
break;
}
}
for (int i = begin+1; i <= begin+paramNames.length; i++){
paramNames[count] = attr.variableName(i);
count++;
}
return paramNames;
} catch (Exception e) {
e.printStackTrace();
}finally{
try {
if(cc != null) cc.detach();
} catch (Exception e2) {
log.error(e2.getMessage());
}
}
return new String[0];
}
}
(编辑:安卓应用网) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |
