研究一周终于出成果啦!
注解
package com.it.annotation;
import java.lang.annotation.*;
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface JwLock {
/**
* 锁的名称
*/
String lockName() default "jwLock";
/**
* 锁的后缀 动态锁名称
* 适用能直接获取的参数 或者对象属性
*/
String lockSuffix() default "";
/**
* 参数索引位置 第一位0
*/
int lockIndex() default 0;
/**
* 锁的有效时间
*/
long expireTime() default -1;
}
AOP 动态获取锁名称 也可以固定锁名称
package com.it.config;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.it.annotation.JwLock;
import com.it.util.RedisUtil;
import lombok.extern.slf4j.Slf4j;
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.lang.reflect.Method;
import java.util.concurrent.TimeUnit;
/**
* @description: 分布式互斥锁
*/
@Slf4j
@Aspect
@Component
public class JwLockAspect {
@Autowired
RedisUtil redisUtil;
@Pointcut("@annotation(com.it.annotation.JwLock)")
public void lockPointCut() {
}
@Around("lockPointCut()")
public void around(ProceedingJoinPoint joinPoint) {
Object[] args = joinPoint.getArgs();
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Method method = methodSignature.getMethod();
JwLock redisLock = method.getAnnotation(JwLock.class);
//获取锁名称
String lockName = redisLock.lockName();
//获取超时时间
long expireTime = redisLock.expireTime();
String lockSuffix = redisLock.lockSuffix();
int lockIndex = redisLock.lockIndex();
if (args.length > 0 && StrUtil.isNotBlank(lockSuffix)) {
if (lockIndex < 0 || lockIndex >= args.length) {
throw new RuntimeException("lockIndex不能小于0或者大于等于参数个数");
}
Object arg = args[lockIndex];
String suffix;
//拼接锁后缀
if (arg instanceof Integer || arg instanceof String || arg instanceof Long) {
suffix = String.valueOf(arg);
} else {
JSONObject jsonObject = JSONUtil.parseObj(JSONUtil.toJsonStr(arg));
suffix = String.valueOf(jsonObject.get(lockSuffix));
}
lockName = lockName + suffix;
}
boolean isLock = redisUtil.tryLock(lockName, expireTime);
try {
if (isLock) {
log.info(Thread.currentThread().getName() + ",锁名称" + lockName + "加锁成功,开始执行业务...");
joinPoint.proceed();
log.info(Thread.currentThread().getName() + ",锁名称" + lockName + "业务执行完成...");
} else {
log.info(Thread.currentThread().getName() + ",锁名称" + lockName + "尝试获取锁失败, 稍后重试!");
throw new RuntimeException("咦 ,姿势不对 ,手速慢了!");
}
} catch (Throwable throwable) {
log.info(Thread.currentThread().getName() + ",锁名称" + lockName + "加锁失败", throwable);
} finally {
//如果该线程还持有该锁,那么释放该锁。如果该线程不持有该锁,说明该线程的锁已到过期时间,自动释放锁
redisUtil.deleteLock(lockName);
}
}
}
package com.it.util;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;
import java.util.*;
import java.util.concurrent.TimeUnit;
@Slf4j
@Data
@Component
public class RedisUtil {
@Autowired
RedisTemplate<String, Object> redisTemplate;
@Autowired
StringRedisTemplate stringRedisTemplate;
ThreadLocal<String> threadLocal = new ThreadLocal<>();
/**
* lua 脚本
*/
public static final String Script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
/**
* 分布式锁 timeout为-1时锁自动续期
*
* @param lockName 锁名称
* @return Boolean
*/
public Boolean setNx(String lockName) {
return setNx(lockName, -1L);
}
/**
* 分布式锁 自定义过期时间
*
* @param lockName 锁名称
* @return Boolean
*/
public Boolean setNx(String lockName, long timeout) {
String uuid = IdUtil.simpleUUID();
if (timeout == -1) {
timeout = 30L;
Boolean isLock = setNx(lockName, timeout);
if (isLock) {
threadLocal.set(uuid);
//锁续期
renewExpireTime(lockName, uuid, timeout);
}
return isLock;
} else {
timeout = timeout <= 0 ? 120 : timeout;
Boolean isLock = stringRedisTemplate.opsForValue().setIfAbsent(lockName, uuid, timeout, TimeUnit.SECONDS);
if (Boolean.TRUE.equals(isLock)) {
threadLocal.set(uuid);
log.info(Thread.currentThread().getName() + "获取锁{}成功,UUID:{}", lockName, uuid);
}
return isLock;
}
}
private void renewExpireTime(String lockName, String uuid, long timeout) {
//自动续期脚本
String script = " if redis.call('exists',KEYS[1],ARGV[1]) == 1 then " +
" return redis.call('expire',KEYS[1],ARGV[2]) " +
" else" +
" return 0 " +
" end ";
log.info(Thread.currentThread().getName() + "锁名称:{},renewExpireTime.uuid:{}", lockName, uuid);
new Timer().schedule(new TimerTask() {
@Override
public void run() {
if (Boolean.TRUE.equals(stringRedisTemplate.execute(new DefaultRedisScript<>(script, Boolean.class), Collections.singletonList(lockName), uuid, String.valueOf(timeout)))) {
Long expire = stringRedisTemplate.getExpire(lockName, TimeUnit.SECONDS);
log.info(Thread.currentThread().getName() + "锁名称:{}续命成功,还有{}秒过期", lockName, expire);
renewExpireTime(lockName, uuid, timeout);
}
;
}
}, (timeout * 1000) / 3);
}
/**
* 释放分布式锁
*
* @param lockName 锁名称
*/
public void deleteLock(String lockName) {
String uuid = threadLocal.get();
if (StrUtil.isNotEmpty(uuid)) {
log.info(Thread.currentThread().getName() + ",锁名称:{},uuid:{}", lockName, uuid);
String curLock = stringRedisTemplate.opsForValue().get(lockName);
if (StrUtil.isNotEmpty(curLock) && uuid.equals(curLock)) {
redisTemplate.delete(lockName);
redisTemplate.execute(new DefaultRedisScript<>(Script, Long.TYPE), Collections.singletonList(lockName), uuid);
threadLocal.remove();
log.info(Thread.currentThread().getName() + ",锁名称" + lockName + "释放锁成功...");
}
}
}
/**
* 分布式锁
*
* @param lockName 锁名称
* @return uuid
*/
public String setNxStr(String lockName, int timeout) {
String uuid = UUID.randomUUID().toString().replace("-", "");
timeout = timeout <= 0 ? 30 : timeout;
Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(lockName, uuid, timeout, TimeUnit.SECONDS);
if (Boolean.TRUE.equals(flag)) {
log.info(Thread.currentThread().getName() + "获取锁{}成功,UUID:{}", lockName, uuid);
return uuid;
}
return null;
}
/**
* 释放分布式锁
*
* @param lockMap 锁名称:uuid
*/
public void deleteLock(Map<String, Object> lockMap) {
if (CollUtil.isNotEmpty(lockMap)) {
lockMap.forEach((key, uuid) -> {
log.info(Thread.currentThread().getName() + ",锁名称:{},uuid:{}", key, uuid);
String curLock = stringRedisTemplate.opsForValue().get(key);
if (StrUtil.isNotEmpty(curLock) && uuid.equals(curLock)) {
redisTemplate.delete(key);
redisTemplate.execute(new DefaultRedisScript<Long>(Script, Long.TYPE), Collections.singletonList(key), uuid);
log.info(Thread.currentThread().getName() + ",锁名称" + key + "释放锁成功...");
}
});
}
}
}
pom
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>item-master</artifactId>
<groupId>org.example</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>redis-lock</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.19.3</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
<!-- Redis依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>3.0.4</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.7</version>
</dependency>
</dependencies>
</project>
演示
Controller
@GetMapping("/tryLock2")
public String tryLock2() {
boolean isLock = redisUtil.setNx(maoTai,30);
try {
if (isLock) {
log.info(Thread.currentThread().getName() + "获取锁 开始执行");
Thread.sleep(3000);
log.info(Thread.currentThread().getName() + "执行结束");
} else {
log.info(Thread.currentThread().getName() + "获取锁失败");
return "fail";
}
} catch (InterruptedException e) {
log.error("获取锁失败" + e.getMessage());
} finally {
redisUtil.deleteLock(maoTai);
}
return "success";
}
@GetMapping("/tryLock3")
public String tryLock3() {
log.info(Thread.currentThread().getName() + "来了...");
testService.tryLock3();
return "success";
}
@PostMapping("/tryLock4")
public String tryLock4(@RequestBody TaskDTO taskDTO) {
testService.tryLock4(taskDTO);
return "success";
}
@GetMapping("/tryLock5")
public String tryLock5() {
Boolean isLock = redisUtil.tryLock(maoTai, -1);
try {
if (isLock) {
log.info(Thread.currentThread().getName() + "获取锁 开始执行");
Thread.sleep(25000);
log.info(Thread.currentThread().getName() + "执行结束");
} else {
log.info(Thread.currentThread().getName() + "获取锁失败");
return "fail";
}
} catch (InterruptedException e) {
log.error(e.getMessage());
} finally {
redisUtil.deleteLock(maoTai);
}
return "success";
}
@GetMapping("/tryLock6")
public String tryLock6() {
testService.tryLock6();
return "success";
}
service
package com.it.service;
import com.it.annotation.JwLock;
import com.it.annotation.RedisLock;
import com.it.entity.TaskDTO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.stereotype.Service;
@Slf4j
@Service
public class TestService {
@RedisLock(lockName = "redis-lock")
public void tryLock3() {
log.info(Thread.currentThread().getName() + "获取锁 开始执行");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
log.info(e.getMessage());
}
log.info(Thread.currentThread().getName() + "执行结束");
}
/**
*动态获取锁名称
*/
@JwLock(lockName = "JwLock",lockSuffix = "dataPart")
public void tryLock4(TaskDTO taskDTO) {
log.info(Thread.currentThread().getName() + "获取锁 开始执行");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
log.info(e.getMessage());
}
log.info(Thread.currentThread().getName() + "执行结束");
}
@JwLock(lockName = "JwLock")
public void tryLock6() {
try {
Thread.sleep(35000);
} catch (InterruptedException e) {
log.info(e.getMessage());
}
}
}
package com.it.entity;
import lombok.Data;
@Data
public class TaskDTO {
String taskId;
Integer dataPart;
}
用jmeter测试 5个线程同时调用/tryLock4
{
“taskId”:“1”,
“dataPart”:“20230501”
}
动态获取锁名称演示
用jmeter测试 5个线程同时调用/tryLock6
锁续命演示