如果想实现分布式锁请看上一篇文章【这样写redission分布式锁才优雅】
上一篇文章留了一个小尾巴,redission的lock方法是会阻塞的,具有一定的风险,
- 如果获取锁的线程一直不释放锁或者占用锁时间过长,那么其他线程只能一直等待,造成资源浪费甚至死锁
- 如果有心之人发现你的方法存在阻塞,有可能利用这个进行DOS攻击,造成服务器瘫痪
我们翻开lock方法的源码,有这么一个注释:
java
/**
* Acquires the lock with defined <code>leaseTime</code>.
* Waits if necessary until lock became available.
*
* Lock will be released automatically after defined <code>leaseTime</code> interval.
*
* @param leaseTime the maximum time to hold the lock after it's acquisition,
* if it hasn't already been released by invoking <code>unlock</code>.
* If leaseTime is -1, hold the lock until explicitly unlocked.
* @param unit the time unit
*
*/
void lock(long leaseTime, TimeUnit unit);
意思就是说,这是一个获取锁的方法,但是如果锁已经存在会进行等待。这个等待是没有时间限制的,有兴趣可以看一下源码,利用的是while(true)循环。
总觉得不够优雅,还好redission还提供了一个方法叫tryLock,注解如下:
java
/**
* Tries to acquire the lock with defined <code>leaseTime</code>.
* Waits up to defined <code>waitTime</code> if necessary until the lock became available.
*
* Lock will be released automatically after defined <code>leaseTime</code> interval.
*
* @param waitTime the maximum time to acquire the lock
* @param leaseTime lease time
* @param unit time unit
* @return <code>true</code> if lock is successfully acquired,
* otherwise <code>false</code> if lock is already set.
* @throws InterruptedException - if the thread is interrupted
*/
boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException;
注解意思是,获取锁,获取不到就等待,但是waitTime耗尽的时候就不再等待,并返回false。跟lock的区别有两个,一是不会无限等待,甚至可以不等待(waitTime设置为0);二是有返回值,代表是否占用锁成功。
稍加对比,就会发现redission的lock方法就像是synchronized,沉重又不优雅,而tryLock保留lock功能的同时拒绝了无限等待。这两个方法该怎么选用一目了然。
我们回想一下上一篇文章的案例,订单支付成功时,调用发货方法。同一笔订单只能发货一次,所以我们根本没必要等锁,当发现相同订单号已经占用锁的时候,我们完全可以第一时间退出,节约资源。其实大多数使用分布式锁的场景都是如此,所以我们对上文中的注解进行改造如下:
改造注解(增加waitTime参数)
c
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RedisLock {
/**
* redis锁 名字
*/
String lockName() default "";
/**
* redis锁 key 支持spel表达式
*/
String key() default "";
/**
* 过期秒数,默认为5毫秒
*
* @return 轮询锁的时间
*/
int expire() default 5000;
/**
* 等锁时间,默认0秒
* @return 等锁时间
*/
int waitTime() default 0;
/**
* 超时时间单位
*
* @return 秒
*/
TimeUnit timeUnit() default TimeUnit.MILLISECONDS;
}
改造切面实现(lock改为tryLock)
c
@Around("@annotation(redisLock)")
public Object around(ProceedingJoinPoint joinPoint, RedisLock redisLock) throws Throwable {
String spel = redisLock.key();
String lockName = redisLock.lockName();
RLock rLock = redissonClient.getLock(getRedisKey(joinPoint,lockName,spel));
boolean isLock = false;
Object result = null;
try{
isLock = rLock.tryLock(redisLock.waitTime(),redisLock.expire(),redisLock.timeUnit());
if (isLock) {
//执行方法
result = joinPoint.proceed();
} else {
log.info("获取锁{}失败", getRedisKey(joinPoint, lockName, spel));
}
} finally {
rLock.unlock();
}
return result;
}
注解的使用不做改动,waitTime不赋值就会使用默认的0秒,也就是获取不到锁立刻退出,节约资源。(见【这样写redission分布式锁才优雅】)