1. 简介
JDK: 1.8~21
Redis: 3.0~7.2
基于Redis的Java分布式可重入锁对象,实现了Lock接口。使用pub/sub通道通知所有Redisson实例中等待获取锁的其他线程。
如果获得锁的Redisson实例崩溃,那么该锁将永远挂起,处于获得状态。为了避免这种情况,Redisson维护了锁看门狗,它在锁持有者Redisson实例活着的时候延长锁过期时间。默认情况下,锁看门狗超时时间为30秒,可通过配置修改。lockWatchdogTimeout设置。
基本使用
RLock lock = redisson.getLock("myLock");lock.lock();// 获得锁并在10秒后自动解锁lock.lock(10, TimeUnit.SECONDS);// 等待获取锁时间100秒,10秒后自动解锁boolean res = lock.tryLock(100, 10, TimeUnit.SECONDS);if (res) {try {// todo} finally {lock.unlock() ;}}
异步接口使用
RLock rLock = redisson.getLock(key) ;rLock.lock() ;RFuture<Boolean> lockFuture = rLock.tryLockAsync(100, 10, TimeUnit.SECONDS);lockFuture.whenComplete((res, exception) -> {// todorLock.unlockAsync();});
以上是简单的使用示例,关于Redisson的详细使用请查看官方文档。
<dependency><groupId>org.redisson</groupId><artifactId>redisson-spring-boot-starter</artifactId><version>3.18.0</version></dependency>
spring:redis:redisson:: classpath:redisson.yaml
---singleServerConfig:idleConnectionTimeout: 10000connectTimeout: 10000timeout: 3000retryAttempts: 3retryInterval: 1500password: xxxooosubscriptionsPerConnection: 5clientName: nulladdress: "redis://127.0.0.1:6379"subscriptionConnectionMinimumIdleSize: 1subscriptionConnectionPoolSize: 50connectionMinimumIdleSize: 24connectionPoolSize: 64database: 14dnsMonitoringInterval: 5000threads: 16nettyThreads: 32codec: !<org.redisson.codec.Kryo5Codec> {}transportMode: "NIO"
@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.METHOD})public @interface DLock {/**分布式锁的key*/String value() default "" ;}
public class DistributedLockAspect {private final RedissonClient redisson ;public DistributedLockAspect(RedissonClient redisson) {this.redisson = redisson ;}("@annotation(lock)")private void lockpc(DLock lock) {}("lockpc(lock)")public Object lockAround(ProceedingJoinPoint pjp, DLock lock) throws Throwable {MethodSignature signature = (MethodSignature) pjp.getSignature() ;Method method = signature.getMethod() ;Object[] args = pjp.getArgs() ;String key = lock.value() ;if ("".equals(key)) {// 根据当前的类名+方法参数信息生成keykey = configKey(signature.getDeclaringType(), method).replaceAll("[^a-zA-Z0-9]", "") ;} else {// 支持SpEL表达式StandardEvaluationContext context = new StandardEvaluationContext() ;// 这里将当前执行的方法参数信息都存入到SpEL执行的上下文中DefaultParameterNameDiscoverer discoverer = new DefaultParameterNameDiscoverer() ;String[] parameterNames = discoverer.getParameterNames(method) ;for (int i = 0, len = parameterNames.length; i < len; i++) {context.setVariable(parameterNames[i], args[i]) ;}ExpressionParser parser = new SpelExpressionParser() ;Expression expression = parser.parseExpression(key) ;key = expression.getValue(context, String.class) ;}RLock rLock = redisson.getLock(key) ;rLock.lock() ;try {Object ret = pjp.proceed() ;return ret ;} finally {rLock.unlock() ;}}// 根据类,方法信息生成分布式锁的keyprivate String configKey(Class<?> targetType, Method method) {StringBuilder builder = new StringBuilder();builder.append(targetType.getSimpleName());builder.append('#').append(method.getName()).append('(');for (Class<?> param : method.getParameterTypes()) {builder.append(param.getSimpleName()).append(',');}if (method.getParameterTypes().length > 0) {builder.deleteCharAt(builder.length() - 1);}return builder.append(')').toString();}}
// 通过SpEL表达式定义分布式锁的key,根据当前的请求参数// key统一添加前缀'person:'@DLock("'person:' + #person.id + ':' + #cateId")public void create(Person person, Integer cateId) {System.out.printf("当前执行线程: %s%n", Thread.currentThread().getName()) ;// todosleep(5) ;}
@Testpublic void testCreate() {int size = 5 ;CountDownLatch cdl = new CountDownLatch(size) ;CountDownLatch nums = new CountDownLatch(size) ;Thread[] threads = new Thread[size] ;for (int i = 0; i < size; i++) {final int n = i ;threads[i] = new Thread(() -> {try {nums.countDown() ;nums.await() ;} catch (InterruptedException e) {e.printStackTrace();}Person person = new Person();person.setId(2L) ;person.setName("张三") ;lockService.create(person, 666) ;cdl.countDown() ;}, "T - " + n) ;}for (int i = 0; i < size; i++) {threads[i].start() ;}try {cdl.await() ;} catch (InterruptedException e) {e.printStackTrace();}}
当前时间戳: 1712041261076, 当前执行线程: T - 4当前时间戳: 1712041266080, 当前执行线程: T - 0当前时间戳: 1712041271090, 当前执行线程: T - 3当前时间戳: 1712041276102, 当前执行线程: T - 1当前时间戳: 1712041281119, 当前执行线程: T - 2
根据程序每隔5s后其它线程执行。

模拟不同的请求参数(请求参数不同的情况)
单元测试进行如下修改
Person person = new Person();person.setId(n + 0L) ;person.setName("张三 - " + n) ;lockService.create(person, n) ;
输出结果
当前时间戳: 1712041534160, 当前执行线程: T - 1当前时间戳: 1712041534160, 当前执行线程: T - 4当前时间戳: 1712041534160, 当前执行线程: T - 0当前时间戳: 1712041534160, 当前执行线程: T - 3当前时间戳: 1712041534160, 当前执行线程: T - 2
由于参数不同,所以这里同时输出。redis中的key如下:

每个线程都不同的key。
注意:在切面中并没有对SpEL执行异常进行处理。
以上是本篇文章的全部内容,希望对你有帮助。
完毕!!!



