概述
为了保证一个方法在高并发情况下的同一时间只能被同一个线程执行,在传统单体应用单机部署的情况下,可以使用Java并发处理相关的API(如ReentrantLcok或synchronized)进行互斥控制。但是,随着业务发展的需要,原单体单机部署的系统被演化成分布式系统后,由于分布式系统多线程、多进程并且分布在不同机器上,这将使原单机部署情况下的并发控制锁策略失效,为了解决这个问题就需要一种跨JVM的互斥机制来控制共享资源的访问,这就是分布式锁要解决的问题。
本方案将使用redis单线程的特性,将锁存放在redis中,多线程从redis中抢占这个锁。
引入依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
</dependencies>
redis属性配置
spring:
redis:
host: 192.168.1.200
password: 123123
port: 6379
database: 1
创建锁注解
import java.lang.annotation.*;
import java.util.concurrent.TimeUnit;
/**
* 锁的注解
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface LockAop {
/**
* redis 锁key
*
* @return redis 锁key
*/
String lockKey() default "";
/**
* 过期秒数,默认为5秒
*
* @return 轮询锁的时间
*/
int expire() default 5;
/**
* 超时时间单位
*
* @return 秒
*/
TimeUnit timeUnit() default TimeUnit.SECONDS;
}
锁拦截器(AOP)
import com.demo.annotation.LockAop ;
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.context.annotation.Configuration;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.util.StringUtils;
import java.lang.reflect.Method;
/**
* 锁拦截器
*/
@Aspect
@Configuration
public class LockAspectInterceptor {
@Autowired
public LockMethodInterceptor(StringRedisTemplate lockRedisTemplate, CacheKeyGenerator cacheKeyGenerator) {
this.lockRedisTemplate = lockRedisTemplate;
this.cacheKeyGenerator = cacheKeyGenerator;
}
private final StringRedisTemplate lockRedisTemplate;
private final CacheKeyGenerator cacheKeyGenerator;
@Around("execution(public * *(..)) && @annotation(com.demo.annotation.LockAop )")
public Object interceptor(ProceedingJoinPoint pjp) {
MethodSignature signature = (MethodSignature) pjp.getSignature();
Method method = signature.getMethod();
LockAop lock = method.getAnnotation(LockAop.class);
if (StringUtils.isEmpty(lock.getLocakKey())) {
throw new RuntimeException("lock key can't be null...");
}
//setIfAbsent方法特性:key不存在才能设置成功,返回true,存在则不设置,返回false
final Boolean success = lockRedisTemplate.opsForValue().setIfAbsent(lock.getLocakKey(), "lockKey");
if (success) {
lockRedisTemplate.expire(lock.getLocakKey(), lock.expire(), lock.timeUnit());
} else {
//可抛出自定义 异常;
throw new RuntimeException("当前资源已被抢占,请稍后重试!");
}
try {
try {
//保证了该逻辑方法当前仅有一个线程在处理
return pjp.proceed();
} catch (Throwable throwable) {
throw new RuntimeException("系统异常");
}
} finally {
//处理完毕,释放锁
lockRedisTemplate.delete(lockKey);
}
}
}
service层
/**
*测试锁,抢占
*/
@Service
public class testService{
//expire:为避免锁过期时间太早,演示的时候可设置为60s,生成环境一般设置小一点
@LockAop(lockKey="lock:test",expire=60)
public String getLock(){
try {
//为方便演示,线程拿到锁进入该方法后,先睡眠60秒,在这60s内,其他线程(请求)都拿不到锁
Thread.sleep(60);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "已拿到锁";
}
}
然后在写个controller调用该方法即可测试啦,这里就不具体实现了。