基于Redis的Redisson分布式可重入公平锁也是实现了java.util.concurrent.locks.Lock接口的一种RLock对象。
基于Redis的Redisson分布式可重入公平锁也是实现了java.util.concurrent.locks.Lock接口的一种RLock对象。同时还提供了异步(Async)、反射式(Reactive)和RxJava2标准的接口。它保证了当多个Redisson客户端线程同时请求加锁时,优先分配给先发出请求的线程,所有请求线程会在一个队列中排队。
如果负责储存这个分布式锁的Redis节点宕机以后,而且这个锁正好处于锁住的状态时,这个锁会出现锁死的状态。为了避免这种情况的发生,Redisson内部提供了一个监控锁的看门狗,它的作用是在Redisson实例被关闭前,不断的延长锁的有效期。默认情况下,看门狗的检查锁的超时时间是30秒钟,也可以通过修改Config.lockWatchdogTimeout来另行指定。
另外Redisson还通过加锁的方法提供了leaseTime的参数来指定加锁的时间。超过这个时间后锁便自动解开了。
1. 简单示例使用方法:
RLock fairLock = redisson.getFairLock("myLock");
// 尝试加锁,最多等待100秒,上锁以后10秒自动解锁
boolean res = fairLock.tryLock(100, 10, TimeUnit.SECONDS);
if (res) {
try {
...你的业务
} finally {
//释放锁
lock.unlock();
}
}
2. 工具封装
import com.yunhx.commons.exception.BusinessException;
import lombok.extern.slf4j.Slf4j;
import org.redisson.RedissonMultiLock;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Component;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
@Slf4j
@Component
public class LockManager {
@Resource
private RedissonClient redissonClient;
//公平锁(Fair Lock)
private static final ThreadLocal<ConcurrentMap<String, RLock>> FAIR_LOCK_THREAD_LOCAL = new ThreadLocal<>();
//联锁(MultiLock)
private static final ThreadLocal<RedissonMultiLock> MULTI_LOCK_THREAD_LOCAL = new ThreadLocal<>();
/**
* 给KEY上分布式公平锁
*/
public boolean fairTryLock(String key) {
RLock fairLock = redissonClient.getFairLock(key);
boolean tryLock = fairLock.tryLock();
return setFairLockToThreadLocal(key, fairLock, tryLock);
}
/**
* 给KEY上分布式公平锁
*
* @param leaseTime 秒 得到锁后超过这个时间会自动解锁
*/
public boolean fairTryLock(String key, long leaseTime) {
try {
RLock fairLock = redissonClient.getFairLock(key);
boolean tryLock = fairLock.tryLock(leaseTime, TimeUnit.SECONDS);
return setFairLockToThreadLocal(key, fairLock, tryLock);
} catch (Exception e) {
log.error("{}获取公平锁失败", key, e);
throw new BusinessException("获取公平锁失败");
}
}
/**
* 给KEY上分布式公平锁
*
* @param waitTime 秒 这个时间后还没获到锁得将放弃取锁
* @param leaseTime 秒 得到锁后超过这个时间会自动解锁
*/
public boolean fairTryLock(String key, long waitTime, long leaseTime) {
try {
RLock fairLock = redissonClient.getFairLock(key);
boolean tryLock = fairLock.tryLock(waitTime, leaseTime, TimeUnit.SECONDS);
return setFairLockToThreadLocal(key, fairLock, tryLock);
} catch (Exception e) {
log.error("{}获取公平锁失败", key, e);
throw new BusinessException("获取公平锁失败");
}
}
/**
* 解公平锁
*/
public void fairUnlock(String key) {
ConcurrentMap<String, RLock> map = FAIR_LOCK_THREAD_LOCAL.get();
RLock rLock = map.get(key);
if (rLock != null && rLock.isHeldByCurrentThread()) {
rLock.unlock();
}
map.remove(key);
if (map.size() < 1){
FAIR_LOCK_THREAD_LOCAL.remove();
}
}
/**
* 解公平锁
*/
public void fairUnlockAll() {
for (RLock rLock : FAIR_LOCK_THREAD_LOCAL.get().values()) {
if (rLock != null && rLock.isHeldByCurrentThread()) {
rLock.unlock();
}
}
FAIR_LOCK_THREAD_LOCAL.remove();
}
private boolean setFairLockToThreadLocal(String key, RLock fairLock, boolean tryLock) {
if (tryLock) {
ConcurrentMap<String, RLock> concurrentMap = FAIR_LOCK_THREAD_LOCAL.get();
if (concurrentMap == null){
concurrentMap = new ConcurrentHashMap<>(1);
FAIR_LOCK_THREAD_LOCAL.set(concurrentMap);
}
concurrentMap.put(key, fairLock);
}
return tryLock;
}
/**
* 给KEYS上分布式联锁
*/
public boolean multiTryLock(String... keys) {
RLock[] rLocks = new RLock[keys.length];
for (int i = 0; i < keys.length; i++) {
rLocks[i] = redissonClient.getLock(keys[i]);
}
RedissonMultiLock lock = new RedissonMultiLock(rLocks);
boolean tryLock = lock.tryLock();
return setMultiLockToThreadLocal(lock, tryLock);
}
/**
* 给KEY上分布式联锁
*
* @param leaseTime 秒 得到锁后超过这个时间会自动解锁
*/
public boolean multiTryLock(long leaseTime, String... keys) {
try {
RLock[] rLocks = new RLock[keys.length];
for (int i = 0; i < keys.length; i++) {
rLocks[i] = redissonClient.getLock(keys[i]);
}
RedissonMultiLock lock = new RedissonMultiLock(rLocks);
boolean tryLock = lock.tryLock(leaseTime, TimeUnit.SECONDS);
return setMultiLockToThreadLocal(lock, tryLock);
} catch (Exception e) {
log.error("{}获取锁失败", keys, e);
throw new BusinessException("获取联锁失败");
}
}
/**
* 给KEY上分布式联锁
*
* @param waitTime 秒 这个时间后还没获到锁得将放弃取锁
* @param leaseTime 秒 得到锁后超过这个时间会自动解锁
*/
public boolean multiTryLock(long waitTime, long leaseTime, String... keys) {
try {
RLock[] rLocks = new RLock[keys.length];
for (int i = 0; i < keys.length; i++) {
rLocks[i] = redissonClient.getLock(keys[i]);
}
RedissonMultiLock lock = new RedissonMultiLock(rLocks);
boolean tryLock = lock.tryLock(waitTime, leaseTime, TimeUnit.SECONDS);
return setMultiLockToThreadLocal(lock, tryLock);
} catch (Exception e) {
log.error("{}获取锁失败", keys, e);
throw new BusinessException("获取联锁失败");
}
}
/**
* 解联锁
*/
public void multiUnlock() {
RedissonMultiLock rLock = MULTI_LOCK_THREAD_LOCAL.get();
if (rLock != null) {
rLock.unlock();
}
MULTI_LOCK_THREAD_LOCAL.remove();
}
private boolean setMultiLockToThreadLocal(RedissonMultiLock fairLock, boolean tryLock) {
if (tryLock) {
MULTI_LOCK_THREAD_LOCAL.set(fairLock);
}
return tryLock;
}
}
3. 工具使用
@Component
public class LockManagerTest {
@Resource
private LockManager lockManager;
public void synTerminalTransQueryFromStartTime() {
//一直阻塞尝试获取锁,10s后还未获得锁,则放弃取锁,返回false
String key = "myLock";
boolean res = lockManager.fairTryLock(key,10,60);
try {
if(res){
业务逻辑开始
}
}catch (Exception e){
e.printStackTrace();
}finally {
//释放锁
lockManager.fairUnlock(key);
}
}
}
4. 使用AOP获取锁,aop的几个注解执行流程自己度娘一下,这里不做介绍
4.1 FairLockAspect切面
import lombok.extern.log4j.Log4j2;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import java.util.concurrent.TimeUnit;
@Log4j2
@Aspect
public class FairLockAspect {
@Resource
private RedissonClient redissonClient;
@Around("@annotation(fairLockAnn)")
public Object fairTryLock(ProceedingJoinPoint pjp, LockFair fairLockAnn) throws Throwable {
String key = fairLockAnn.key();
final int index = fairLockAnn.keyParamIndex();
final boolean keyParam = fairLockAnn.paramKey();
if (keyParam) {
Object[] args = pjp.getArgs();
if (index < 0 || args == null || args.length < (index + 1) || args[index] == null) {
log.warn(">> 未找到参数或参数值为空,无法进行上锁: {}, keyParam=true,keyParamIndex={}", pjp, index);
throw new RuntimeException("未找到参数或参数值为空,无法进行上锁");
} else if (isValidKeyParamType(args[index])) {
key = key + args[index];
} else {
log.warn(">> 设置的参数不是string/long/int/short/byte类型, 无法进行上锁: {}", pjp);
throw new RuntimeException("设置的参数类型不正确,无法进行上锁");
}
}
RLock fairLock = redissonClient.getFairLock(key);
//秒 这个时间后还没获到锁得将放弃取锁
final long waitTime = fairLockAnn.waitTime();
//秒 得到锁后超过这个时间会自动解锁
final long leaseTime = fairLockAnn.leaseTime();
boolean tryLock = fairLock.tryLock(waitTime, leaseTime, TimeUnit.SECONDS);
if(!tryLock){
throw new RuntimeException("获取公平锁失败,请稍后再试");
}
try {
return pjp.proceed();
} finally {
if (fairLock.isHeldByCurrentThread()){
fairLock.unlock();
}
}
}
private boolean isValidKeyParamType(Object param) {
return (param instanceof String) || (param instanceof Long) || (param instanceof Integer) || (param instanceof Short)
|| (param instanceof Byte);
}
}
4.2 LockFair注解
import java.lang.annotation.*;
/**
* 公平锁注解
*/
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LockFair {
/**
* 缓存key
*
* @return
*/
String key();
/**
* 秒 这个时间后还没获到锁得将放弃取锁
*/
long waitTime() default 5;
/**
* 秒 得到锁后超过这个时间会自动解锁
*/
long leaseTime() default 10;
/**
* 是否使用方法的参数作为缓存key的组成
*/
boolean paramKey() default false;
/**
* 要用来作为缓存key组成的参数索引(从0开始),
* 该索引对应的参数必须为string/Long/Integer/Short/Byte,
* 仅当{@link #paramKey}为{@code true}时有用
*/
int keyParamIndex() default 0;
}
4.3 注解的使用示例
@Component
public class LockManagerTest {
@LockFair(key = CacheKeys.ADAPAY_PAYMENT_NOTIFY, waitTime = 20, leaseTime = 20,paramKey = true)
public void synTerminalTransQueryFromStartTime() {
//你的业务逻辑 TODO
}
}