<!-- json -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.49</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.3.7</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.util.concurrent.TimeUnit;
@Component
public class RedisUtil {
private RedisTemplate redisTemplate;
@Autowired
public void setRedisTemplate(RedisTemplate<String, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
}
/**
* 指定缓存失效时间
*
* @param key 键
* @param time 时间(秒)
* @return
*/
public boolean expire(String key, long time) {
try {
if (time > 0) {
redisTemplate.expire(key, time, TimeUnit.SECONDS);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 给指定key存放 value,指定的key若已存在值 失败
* redis分布式锁
*
* @param key
* @param value
* @return
*/
public boolean setIfAbsent(String key, Object value) {
try {
return redisTemplate.opsForValue().setIfAbsent(key, value);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 删除缓存
*
* @param key 可以传一个值 或多个
*/
@SuppressWarnings("unchecked")
public void del(String... key) {
if (key != null && key.length > 0) {
if (key.length == 1) {
redisTemplate.delete(key[0]);
} else {
redisTemplate.delete(CollectionUtils.arrayToList(key));
}
}
}
/**
* 普通缓存放入并设置时间
*
* @param key 键
* @param value 值
* @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
* @return true成功 false 失败
*/
public boolean set(String key, Object value, long time) {
try {
if (time > 0) {
redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
} else {
set(key, value);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 普通缓存放入
*
* @param key 键
* @param value 值
* @return true成功 false失败
*/
public boolean set(String key, Object value) {
try {
redisTemplate.opsForValue().set(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 普通缓存获取
*
* @param key 键
* @return 值
*/
public Object get(String key) {
return key == null ? null : redisTemplate.opsForValue().get(key);
}
}
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface RedisLock {
/**
* RedisKeyPrefix.LOCK + key
* key 默认 class.method
* @return
*/
String key() default "";
// 过期时长
long duration() default 1000L;
// key 是否加上入参
String keyAddArgs() default "";
// 并发策略,true 直接抛异常, false 重复获取 锁
boolean isThrowMsg() default true;
}
方式一 自旋锁实现
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
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.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* 并发redis分布式锁
*/
@Component
@Aspect
@Slf4j
public class RedisLockAop {
@Autowired
RedisUtil redisUtil;
/**
* 切点注解拦截
*/
@Pointcut("@annotation(RedisLock)")
public void aspect() {
}
/**
* redis锁
* key = RedisKeyPrefix.LOCK + redisLock.key
* redisLock.key 默认 class.method
*/
@Around("@annotation(redisLock)")
public Object around(ProceedingJoinPoint point, RedisLock redisLock) throws Throwable {
log.info("redis锁执行开始========");
// redis key
String key = redisLock.key();
if (StrUtil.isBlank(key)) {
String targetClassName = point.getTarget().getClass().getName();
String targetMethodName = point.getSignature().getName();
key = targetClassName + "." + targetMethodName;
}
key = RedisKeyPrefix.LOCK + key;
// key + 入参
if (point.getArgs().length > 0) {
RedisLock method = getMethod(point);
String args = getAnnotationValue(point, method.keyAddArgs());
key += ":args:" + args ;
}
log.info("key :{}", key);
// 获取锁,若失败,判断是否死锁,是:释放并重新获取锁
while (true) {
boolean aBoolean = redisUtil.setIfAbsent(key, new Date(System.currentTimeMillis() + redisLock.duration()).getTime());
if (!aBoolean) {
Object o = redisUtil.get(key);
if (o == null) {
// 不存在key 锁已释放,重新获取
continue;
}
long oldExpireTime = Long.parseLong((o.toString()));
long currentTime = System.currentTimeMillis();
// 判断锁是否过期
if (currentTime >= oldExpireTime) {
// 锁已过期
redisUtil.del(key);
continue;
}
}
if (aBoolean) {
log.info("获取到锁");
break;
}
if (redisLock.isThrowMsg()) {
// 没有获取到锁 直接抛异常
throw new RuntimeException("没有获取到锁 直接抛异常");
} else {
// 没有获取到锁 等待5秒尝试再去获取锁
log.info("没有获取到锁 等待5秒尝试再去获取锁");
TimeUnit.SECONDS.sleep(5);
}
}
// 锁获取成功后,指定失效时间,防止死锁
redisUtil.expire(key, redisLock.duration());
Object proceed;
try {
proceed = point.proceed();
} finally {
redisUtil.del(key);
}
log.info("redis锁执行结束========");
return proceed;
}
/**
* 获取注解中对方法的描述信息
*/
public static RedisLock getMethod(JoinPoint joinPoint) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
return signature.getMethod().getAnnotation(RedisLock.class);
}
/**
* 获取注解中传递的动态参数的参数值
* 1.“#{参数1},#{参数2}” 多个参数使用逗号分割
* 2.“#{对象.参数1},#{对象.参数2}” 复杂的参数类型:对象.参数名
* 3. 非动态参数直接返回
* @param joinPoint
* @param paramNames
* @return
*/
public String getAnnotationValue(JoinPoint joinPoint, String paramNames) {
String[] split = paramNames.split(",");
StringBuffer key = new StringBuffer();
for (String paramName : split) {
// 获取方法中所有的参数
Map<String, Object> params = getParams(joinPoint);
// 参数是否是动态的:#{paramName}
if (paramName.matches("^#\\{\\D*\\}")) {
// 获取参数名
paramName = paramName.replace("#{", "").replace("}", "");
// 是否是复杂的参数类型:对象.参数名
if (paramName.contains(".")) {
String[] paramNameSplit = paramName.split("\\.");
// 获取方法中对象的内容
Object object = getValue(params, paramNameSplit[0]);
// 转换为JsonObject
JSONObject jsonObject = (JSONObject) JSONObject.toJSON(object);
// 获取值
Object o = jsonObject.get(paramNameSplit[1]);
key.append(paramName).append(":").append(o).append(":");
}else {
// 简单的动态参数
key.append(paramName).append(":").append(getValue(params, paramName)).append(":");
}
}
}
// 非动态参数直接返回
return key.toString();
}
/**
* 根据参数名返回对应的值
*
* @param map
* @param paramName
* @return
*/
public Object getValue(Map<String, Object> map, String paramName) {
for (Map.Entry<String, Object> entry : map.entrySet()) {
if (entry.getKey().equals(paramName)) {
return entry.getValue();
}
}
return null;
}
/**
* 获取方法的参数名和值
*
* @param joinPoint
* @return
*/
public Map<String, Object> getParams(JoinPoint joinPoint) {
Map<String, Object> params = new HashMap<>(8);
Object[] args = joinPoint.getArgs();
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
String[] names = signature.getParameterNames();
for (int i = 0; i < args.length; i++) {
params.put(names[i], args[i]);
}
return params;
}
}
方式二 redisson 看门狗实现
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.15.3</version>
</dependency>
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
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.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* 并发redis分布式锁
*/
@Component
@Aspect
@Slf4j
public class RedisLockAop {
@Autowired
RedisUtil redisUtil;
@Autowired
RedissonClient redisson;
/**
* 切点注解拦截
*/
@Pointcut("@annotation(RedisLock)")
public void aspect() {
}
/**
* redis锁
* key = RedisKeyPrefix.LOCK + redisLock.key
* redisLock.key 默认 class.method
*/
@Around("@annotation(redisLock)")
public Object around(ProceedingJoinPoint point, RedisLock redisLock) throws Throwable {
// redis key
String key = redisLock.key();
if (StrUtil.isBlank(key)) {
String targetClassName = point.getTarget().getClass().getName();
String targetMethodName = point.getSignature().getName();
key = targetClassName + "." + targetMethodName;
}
key = RedisKeyPrefix.LOCK + key;
// key + 入参
if (point.getArgs().length > 0) {
RedisLock method = getMethod(point);
String args = getAnnotationValue(point, method.keyAddArgs());
key += ":args:" + args ;
}
// 获取一把锁,只要锁的名字一样就是同一把锁
log.info("key :{}", key);
RLock lock = redisson.getLock(key);
// 加锁 自动解锁时间
lock.lock(redisLock.duration(), TimeUnit.SECONDS);
log.info("redis锁执行开始========");
Object proceed;
try {
proceed = point.proceed();
} finally {
if(lock.isLocked()){ // 是否还是锁定状态
if(lock.isHeldByCurrentThread()){ // 时候是当前执行线程的锁
lock.unlock(); // 释放锁
}
}
}
log.info("redis锁执行结束========");
return proceed;
}
/**
* 获取注解中对方法的描述信息
*/
public static RedisLock getMethod(JoinPoint joinPoint) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
return signature.getMethod().getAnnotation(RedisLock.class);
}
/**
* 获取注解中传递的动态参数的参数值
* 1.“#{参数1},#{参数2}” 多个参数使用逗号分割
* 2.“#{对象.参数1},#{对象.参数2}” 复杂的参数类型:对象.参数名
* 3. 非动态参数直接返回
* @param joinPoint
* @param paramNames
* @return
*/
public String getAnnotationValue(JoinPoint joinPoint, String paramNames) {
String[] split = paramNames.split(",");
StringBuffer key = new StringBuffer();
for (String paramName : split) {
// 获取方法中所有的参数
Map<String, Object> params = getParams(joinPoint);
// 参数是否是动态的:#{paramName}
if (paramName.matches("^#\\{\\D*\\}")) {
// 获取参数名
paramName = paramName.replace("#{", "").replace("}", "");
// 是否是复杂的参数类型:对象.参数名
if (paramName.contains(".")) {
String[] paramNameSplit = paramName.split("\\.");
// 获取方法中对象的内容
Object object = getValue(params, paramNameSplit[0]);
// 转换为JsonObject
JSONObject jsonObject = (JSONObject) JSONObject.toJSON(object);
// 获取值
Object o = jsonObject.get(paramNameSplit[1]);
key.append(paramName).append(":").append(o).append(":");
}else {
// 简单的动态参数
key.append(paramName).append(":").append(getValue(params, paramName)).append(":");
}
}
}
// 非动态参数直接返回
return key.toString();
}
/**
* 根据参数名返回对应的值
*
* @param map
* @param paramName
* @return
*/
public Object getValue(Map<String, Object> map, String paramName) {
for (Map.Entry<String, Object> entry : map.entrySet()) {
if (entry.getKey().equals(paramName)) {
return entry.getValue();
}
}
return null;
}
/**
* 获取方法的参数名和值
*
* @param joinPoint
* @return
*/
public Map<String, Object> getParams(JoinPoint joinPoint) {
Map<String, Object> params = new HashMap<>(8);
Object[] args = joinPoint.getArgs();
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
String[] names = signature.getParameterNames();
for (int i = 0; i < args.length; i++) {
params.put(names[i], args[i]);
}
return params;
}
}
问题描述
当我们使用Ression中Lock.lock()方法之后,如果存在线程并发常见情况下,会出现如下异常:
java.lang.IllegalMonitorStateException: attempt to unlock lock, not locked by current thread by node id: 9f178836-f7e1-44fe-a89d-2db52f399c0d thread-id: 22
问题分析
在thread-1还没有结束的时候,也就是在thread-1在获得锁但是还没有释放锁的时候, `thread-2由于被别的线程中断停止了等待从lock.tryLock的阻塞状态中返回继续执行接下来的逻辑,并且由于尝试去释放一个属于线程thread-1的锁而抛出了一个运行时异常导致该线程thread-2结束了, 然而thread-2完成了一系列操作后,线程thread-1才释放了自己的锁. 所以thread-2并没有获得锁,却执行了需要同步的内容,还尝试去释放锁。那解决方式我们就知道了,当前线程加的锁由当前线程去解锁,也就是说当我们使用lock.unlock的时候加上线程的判断即可。
问题解决
RLock lock = redissonClient.getLock(key);
if(lock.isLocked()){ // 是否还是锁定状态
if(lock.isHeldByCurrentThread()){ // 时候是当前执行线程的锁
lock.unlock(); // 释放锁
}
}
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/133926.html