文章目录
前言
通常服务端存在定时器业务时,如果分布式部署,多台同时执行会出现重复的数据,为了避免这种情况,可采用分布式锁。本章节讲解分布式锁RedissonLock以及锁的业务级的应用,内容包括两部分:
- RedissonLock源代码分析
- 锁业务应用
- 接口方法@注解方式 注入锁
- 代码行加锁
一、RedissonLock源代码分析
1.1 尝试加锁
主线:org.redisson.RedissonLock#tryLock(long waitTime, long leaseTime, TimeUnit unit)
public boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException {
long time = unit.toMillis(waitTime);//取得最大等待时间
long current = System.currentTimeMillis();
//取得当前线程id(判断是否可重入锁的关键)
long threadId = Thread.currentThread().getId();
//1.尝试申请锁,返回还剩余的锁过期时间
Long ttl = this.tryAcquire(waitTime, leaseTime, unit, threadId);
if (ttl == null) {
return true;//2.ttl 为空,申请锁成功
} else {
//3.最大等待时间time <= 申请锁的耗时,则申请锁失败
time = time - (System.currentTimeMillis() - current);
if (time <= 0L) {
this.acquireFailed(waitTime, unit, threadId);
return false;
} else {
current = System.currentTimeMillis();
/**
* 4.订阅锁释放事件,并通过await方法阻塞等待锁释放:
* 基于信息量,当锁被其它资源占用时,当前线程通过 Redis 的 channel 订阅锁的释放事件,
* 一旦锁释放会发消息通知待等待的线程进行竞争
*/
RFuture<RedissonLockEntry> subscribeFuture = this.subscribe(threadId);
//await 方法内部是用CountDownLatch来实现阻塞,
//获取subscribe异步执行的结果(应用Netty Future)
if (!subscribeFuture.await(time, TimeUnit.MILLISECONDS)) {
//await(...)返回false,等待时间已经超出获取锁最大等待时间,
//取消订阅并返回获取锁失败
if (!subscribeFuture.cancel(false)) {
subscribeFuture.onComplete((res, e) -> {
if (e == null) {
this.unsubscribe(subscribeFuture, threadId);
}
});
}
this.acquireFailed(waitTime, unit, threadId);
return false;
} else {
//await(...)返回true,进入循环尝试获取锁
try {
time -= System.currentTimeMillis() - current;
if (time <= 0L) {
this.acquireFailed(waitTime, unit, threadId);
boolean var20 = false;
return var20;
} else {
boolean var16;
//5.收到锁释放的信号后,在最大等待时间之内,自旋尝试获取锁
do {
long currentTime = System.currentTimeMillis();
// 再次尝试申请锁
ttl = this.tryAcquire(waitTime, leaseTime, unit, threadId);
if (ttl == null) {
var16 = true;
return var16;//成功获取锁则直接返回true结束循环
}
time -= System.currentTimeMillis() - currentTime;
if (time <= 0L) {
this.acquireFailed(waitTime, unit, threadId);
var16 = false;
return var16;
}
currentTime = System.currentTimeMillis();
//6.阻塞等待锁(通过信号量(共享锁)阻塞,等待解锁消息)
if (ttl >= 0L && ttl < time) {
// 在ttl时间内尝试获取信号量
((RedissonLockEntry)subscribeFuture.getNow())
.getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
} else {
// 在time时间内尝试获取信号量
((RedissonLockEntry)subscribeFuture.getNow()).
getLatch().tryAcquire(time, TimeUnit.MILLISECONDS);
}
time -= System.currentTimeMillis() - currentTime;
} while(time > 0L);
this.acquireFailed(waitTime, unit, threadId);
var16 = false;
return var16;
}
} finally {
//7.无论是否获得锁,都要取消订阅解锁消息
this.unsubscribe(subscribeFuture, threadId);
}
}
}
}
}
订阅:RFuture subscribe(String entryName, String channelName)
发布订阅: 基于信号量的异步订阅机制,订阅指定的Redis频道channel,返回异步Future对象
abstract class PublishSubscribe<E extends PubSubEntry<E>> {
public RFuture<E> subscribe(String entryName, String channelName) {
AsyncSemaphore semaphore = this.service.getSemaphore(new ChannelName(channelName));
RPromise<E> newPromise = new RedissonPromise();
semaphore.acquire(() -> {
if (!newPromise.setUncancellable()) {
semaphore.release();
} else {
......
//创建RedisPubSubListener
RedisPubSubListener<Object> listener = this.createListener(channelName,value);
//订阅通道channelName、信号semaphore,绑定RedisPubSubListener
this.service.subscribe(LongCodec.INSTANCE, channelName, semaphore,
new RedisPubSubListener[]{listener});
}
});
return newPromise;
}
......
//创建监听器,用于监听指定频道的消息和订阅状态
private RedisPubSubListener<Object> createListener(final String channelName,
final E value) {
RedisPubSubListener<Object> listener = new BaseRedisPubSubListener() {
public void onMessage(CharSequence channel, Object message) {
//接收到指定频道的消息时,开始处理消息
if (channelName.equals(channel.toString())) {
PublishSubscribe.this.onMessage(value, (Long)message);
}
}
//当订阅成功时,标记 value 的 Promise 为成功。
public boolean onStatus(PubSubType type, CharSequence channel) {
if (!channelName.equals(channel.toString())) {
return false;
} else if (type == PubSubType.SUBSCRIBE) {
value.getPromise().trySuccess(value);
return true;
} else {
return false;
}
}
};
return listener;
}
}
子线:tryAcquireAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId)
private <T> RFuture<Long> tryAcquireAsync(long waitTime, long leaseTime,
TimeUnit unit, long threadId) {
if (leaseTime != -1L) {
return this.tryLockInnerAsync(waitTime, leaseTime,
unit, threadId, RedisCommands.EVAL_LONG);
} else {
//tryLockInnerAsync-申请锁,申请锁并返回RFuture
RFuture<Long> ttlRemainingFuture = this.tryLockInnerAsync(
waitTime, this.internalLockLeaseTime, TimeUnit.MILLISECONDS,
threadId, RedisCommands.EVAL_LONG);
ttlRemainingFuture.onComplete((ttlRemaining, e) -> {
if (e == null) {
if (ttlRemaining == null) {
this.scheduleExpirationRenewal(threadId);
}
}
});
return ttlRemainingFuture;
}
}
tryLockInnerAsync-尝试异步加锁
<T> RFuture<T> tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId,
RedisStrictCommand<T> command) {
internalLockLeaseTime = unit.toMillis(leaseTime);
/**
* 通过 EVAL 命令执行 Lua 脚本获取锁,保证了原子性
*/
return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, command,
// 1.如果缓存中的key不存在,则执行 hset 命令(hset key UUID+threadId 1),
然后通过 pexpire 命令设置锁的过期时间(即锁的租约时间)
// 返回空值 nil ,表示获取锁成功
"if (redis.call('exists', KEYS[1]) == 0) then " +
"redis.call('hset', KEYS[1], ARGV[2], 1); " +
"redis.call('pexpire', KEYS[1], ARGV[1]); " +
"return nil; " +
"end; " +
// 如果key已经存在,并且value也匹配,表示是当前线程持有的锁,
则执行 hincrby 命令,重入次数加1,并且设置失效时间
"if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
"redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
"redis.call('pexpire', KEYS[1], ARGV[1]); " +
"return nil; " +
"end; " +
//如果key已经存在,但是value不匹配,说明锁已经被其他线程持有,
通过 pttl 命令获取锁的剩余存活时间并返回,至此获取锁失败
"return redis.call('pttl', KEYS[1]);",
//这三个参数分别对应KEYS[1],ARGV[1]和ARGV[2]
Collections.<Object>singletonList(getName()),
internalLockLeaseTime, getLockName(threadId));
}
参数说明:
KEYS[1]就是Collections.singletonList(getName()),表示分布式锁的key
ARGV[1]就是internalLockLeaseTime,即锁的租约时间(持有锁的有效时间),默认30s
ARGV[2]就是getLockName(threadId),是获取锁时set的唯一值 value,即UUID+threadId
scheduleExpirationRenewal-redisson看门狗机制
- 定期续租 Redisson 分布式锁的过期时间,防止锁因超时而被提前释放。
- 创建一个定时任务,每隔internalLockLeaseTime / 3 毫秒执行一次;定时任务中调用 renewExpirationAsync(),异步续期锁,并递归继续下一次续期。
private void renewExpiration() {
ExpirationEntry ee = (ExpirationEntry)EXPIRATION_RENEWAL_MAP.
get(this.getEntryName());
if (ee != null) {
Timeout task = this.commandExecutor.getConnectionManager().
newTimeout(new TimerTask() {
public void run(Timeout timeout) throws Exception {
ExpirationEntry ent = (ExpirationEntry)RedissonLock.EXPIRATION_RENEWAL_MAP.
get(RedissonLock.this.getEntryName());
if (ent != null) {
Long threadId = ent.getFirstThreadId();
if (threadId != null) {
// 更新锁过期时间(lua脚本)
RFuture<Boolean> future =
RedissonLock.this.renewExpirationAsync(threadId);
future.onComplete((res, e) -> {
if (e != null) {
RedissonLock.log.error("Can't update lock " +
RedissonLock.this.getName() + " expiration",
e);RedissonLock.EXPIRATION_RENEWAL_MAP.
remove(RedissonLock.this.getEntryName());
} else {
if (res) {
//递归调用 如果10秒后依然没有解锁,继续更新锁过期时间
RedissonLock.this.renewExpiration();
}
}
});
}
}
}
}, this.internalLockLeaseTime / 3L, TimeUnit.MILLISECONDS);
ee.setTimeout(task);
}
}
............
/*
异步续订分布式锁的过期时间,具体如下:
使用 Lua 脚本保证操作的原子性;
检查当前线程是否持有锁(通过 hexists 判断);
如果持有,则通过 pexpire 延长锁的过期时间;
返回值表示是否成功续订(1 成功,0 失败)
*/
protected RFuture<Boolean> renewExpirationAsync(long threadId) {
return this.evalWriteAsync(this.getName(), LongCodec.INSTANCE,
RedisCommands.EVAL_BOOLEAN,
"if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
"redis.call('pexpire', KEYS[1], ARGV[1]); " +
"return 1; " +
"end; " +
"return 0;",
Collections.singletonList(this.getName()),
this.internalLockLeaseTime, this.getLockName(threadId));
}
............
参数:
this.internalLockLeaseTime = commandExecutor.getConnectionManager()
.getCfg().getLockWatchdogTimeout();//默认30秒
2.2 解锁
org.redisson.RedissonLock#unlock()
protected RFuture<Boolean> unlockInnerAsync(long threadId) {
/**
* 通过 EVAL 命令执行 Lua 脚本获取锁,保证了原子性
*/
return commandExecutor.evalWriteAsync(getName(),
LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
//如果分布式锁存在,但是value不匹配,表示锁已经被其他线程占用,
//当前客户端线程没有持有锁,不能主动解锁
"if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then " +
"return nil;" +
"end; " +
//如果value匹配,将value减1,主要用在重入锁
"local counter=redis.call('hincrby',KEYS[1], ARGV[3],-1);" +
//重入次数减1后的值如果大于0,表示分布式锁有重入过,
//那么只能更新失效时间,还不能删除
"if (counter > 0) then " +
"redis.call('pexpire', KEYS[1], ARGV[2]); " +
"return 0; " +
"else " +
//重入次数减1后的值如果为0,这时就可以删除这个KEY,
//并发布解锁消息,返回1
"redis.call('del', KEYS[1]); " +
"redis.call('publish', KEYS[2], ARGV[1]); " +
"return 1; "+
"end; " +
"return nil;",
Arrays.<Object>asList(getName(), getChannelName()),
LockPubSub.UNLOCK_MESSAGE, internalLockLeaseTime,
getLockName(threadId));
}
参数:
KEYS1:需要加锁的key,这里需要是字符串类型。
KEYS2:redis消息的ChannelName,一个分布式锁对应唯一的一个 channelName:“redisson_lock__channel__{” + getName() + “}”
ARGV1:redis消息体,这里只需要一个字节的标记就可以,主要标记redis的key已经解锁,再结合redis的Subscribe,能唤醒其他订阅解锁消息的客户端线程申请锁。
ARGV2:锁的超时时间,防止死锁
ARGV3 :锁的唯一标识,也就是刚才介绍的 id(UUID.randomUUID()) + “:” + threadId
删除过期信息
void cancelExpirationRenewal() {
Timeout task = expirationRenewalMap.remove(getEntryName());
if (task != null) {
task.cancel();
}
}
二、锁业务应用
1.服务层方法@注解方式 注入锁
1.1 定义DistributedLock @注解类
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DistributedLock {
String key();
int timeout() default 10; // 默认超时时间10秒
int maxExecuteTime() default 30;
TimeUnit timeUnit() default TimeUnit.SECONDS;
}
1.2 定义DistributedLockAspect 切片类
拦截带有 @DistributedLock 注解的方法。
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.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.context.expression.MethodBasedEvaluationContext;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;
@Aspect
@Component
@Slf4j
public class DistributedLockAspect {
@Resource
private RedissonClient redissonClient;
private final ExpressionParser parser = new SpelExpressionParser();
private final DefaultParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
@Around("@annotation(distributedLock)")
public Object around(ProceedingJoinPoint joinPoint, DistributedLock distributedLock) throws Throwable {
String methodName = joinPoint.getSignature().getName();
String dynamicKey = parseSpEL(joinPoint, distributedLock.key());
long timeout = distributedLock.timeout();
long maxExecuteTime = distributedLock.maxExecuteTime();
TimeUnit timeUnit = distributedLock.timeUnit();
//解析key
log.info("[锁切面] 方法 {} 尝试获取锁,key={}, timeout={}{}", methodName, dynamicKey, timeout, timeUnit.name());
RLock lock = redissonClient.getLock(dynamicKey);
boolean isLocked = false;
try {
// 尝试获取锁
isLocked = lock.tryLock(timeout, maxExecuteTime, timeUnit);
if (!isLocked) {
log.error("[Redisson锁] 获取锁失败,key={}", dynamicKey);
throw new RuntimeException("系统繁忙,请重试");
}
log.info("[Redisson锁] 锁获取成功,key={}", dynamicKey);
return joinPoint.proceed(); // 执行业务方法
} finally {
if (isLocked && lock.isHeldByCurrentThread()) {
lock.unlock();
log.info("[Redisson锁] 锁已释放,key={}", dynamicKey);
}
}
}
/**
* 解析 SpEL 表达式
*/
private String parseSpEL(ProceedingJoinPoint joinPoint, String spEL) {
if (!spEL.contains("#")) {
return spEL;
}
Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
Object[] args = joinPoint.getArgs();
// 创建 SpEL 上下文
EvaluationContext context = new MethodBasedEvaluationContext(null, method, args, parameterNameDiscoverer);
// 解析表达式
Expression expression = parser.parseExpression(spEL);
return expression.getValue(context, String.class);
}
}
1.3 尝试获取锁代码片断
@Around("@annotation(distributedLock)")
public Object around(ProceedingJoinPoint joinPoint,
DistributedLock distributedLock) {
......
String dynamicKey = parseSpEL(joinPoint,
distributedLock.key());
......
RLock lock = redissonClient.getLock(dynamicKey);
......
// 尝试获取锁
isLocked = lock.tryLock(timeout, maxExecuteTime, timeUnit);
if (!isLocked) {
log.error("[Redisson锁] 获取锁失败,key={}", dynamicKey);
}
else{
log.info("[Redisson锁] 锁获取成功,key={}", dynamicKey);
return joinPoint.proceed(); // 执行业务方法
}
}
............
package org.redisson;
public class Redisson implements RedissonClient {
public RLock getLock(String name) {
return new RedissonLock(this.connectionManager
.getCommandExecutor(), name);
}
}
1.4 释放锁代码片断
@Around("@annotation(distributedLock)")
public Object around(ProceedingJoinPoint joinPoint,
DistributedLock distributedLock) {
............
finally {
if (isLocked && lock.isHeldByCurrentThread()) {
lock.unlock();
log.info("[Redisson锁] 锁已释放,key={}", dynamicKey);
}
}
}
1.5 服务层注入锁@注解
锁定方法、业务,同一时刻点 或 某一时段内 不可重复执行。
//订单job服务实现类OrderJobServiceImpl
@Service
@RequiredArgsConstructor
@Slf4j
@RefreshScope
public class OrderJobServiceImpl {
/**
*
* [描述]: 执行订单创建任务
* 具备分布式锁防止并发执行,锁超时timeout为30秒
* @date 2025/6/16 14:09
* @param id
* @param updateNextExecuteDate
* @param executeAtDate
*/
@Override
@DistributedLock(key = "#id", timeout = 30)
public void execute(Long id) {
............
//某一时刻点执行订单创建任务
............
}
2.代码行加锁
2.1 pom.xml文件引入redisson、redis
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.14.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
</dependency>
2.2 配置RedissonProperties属性类
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
@Data
@Configuration
public class RedissonProperties {
@Value("${spring.redis.host:}")
private String redisHost;
@Value("${spring.redis.port:}")
private String redisPort;
@Value("${spring.redis.sentinel.nodes:}")
private String redisSentinelNodes;
@Value("${spring.redis.cluster.nodes:}")
private String redisClusterNodes;
@Value("${spring.redis.sentinel.master:}")
private String redisMasterName;
@Value("${spring.redis.password:}")
private String password;
private String springRedis = "spring.redis";
private int timeout = 10000;
private int connectionPoolSize = 64;
private int connectionMinimumIdleSize = 10;
private int slaveConnectionPoolSize = 250;
private int masterConnectionPoolSize = 250;
public String[] getRedisSentinelNodes() {
return redisSentinelNodes.contains(springRedis) ? null : redisSentinelNodes.split(",");
}
public String[] getRedisClusterNodes() {
return redisClusterNodes.contains(springRedis) ? null : redisClusterNodes.split(",");
}
}
2.3 Redisson装配类
- 初始化RedissonClient
- 装配锁Redisson实现类RedissonDistributedLockerServiceImpl,并将实例注入到RedissLockUtil中。
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
/**
* @desc 装配类
*/
@Configuration
@ConditionalOnClass(Config.class)
public class RedissonAutoConfiguration {
@Resource
private RedissonProperties redissonProperties;
private static final String REDIS_PROTOCOL_PREFIX = "redis://";
/**
* @version 哨兵模式自动装配
*/
private Config redissonMasterSentinel() {
String[] nodes = redissonProperties.getRedisSentinelNodes();
if (null == nodes || nodes.length == 0 || "".equals(nodes[0])) {
return null;
}
List<String> clusterNodes = new ArrayList<>();
for (String node : nodes) {
clusterNodes.add(REDIS_PROTOCOL_PREFIX + node);
}
Config config = new Config();
config.useSentinelServers().addSentinelAddress(clusterNodes.toArray(new String[0]))
.setMasterName(redissonProperties.getRedisMasterName())
.setTimeout(redissonProperties.getTimeout())
.setPassword(redissonProperties.getPassword())
.setMasterConnectionPoolSize(redissonProperties.getMasterConnectionPoolSize())
.setSlaveConnectionPoolSize(redissonProperties.getSlaveConnectionPoolSize());
return config;
}
/**
* @version 集群模式自动装配
*/
private Config redissonClusterSentinel() {
String[] nodes = redissonProperties.getRedisClusterNodes();
if (null == nodes || nodes.length == 0 || "".equals(nodes[0])) {
return null;
}
List<String> clusterNodes = new ArrayList<>();
for (String node : nodes) {
clusterNodes.add(REDIS_PROTOCOL_PREFIX + node);
}
Config config = new Config();
config.useClusterServers()
.setScanInterval(1000)
.setTimeout(redissonProperties.getTimeout())
.setPassword(redissonProperties.getPassword())
.addNodeAddress(clusterNodes.toArray(new String[0]))
.setMasterConnectionPoolSize(redissonProperties.getMasterConnectionPoolSize())
.setSlaveConnectionPoolSize(redissonProperties.getSlaveConnectionPoolSize());
return config;
}
/**
* 单机模式自动装配
* @return
*/
private Config redissonAddressSingle() {
Config config = new Config();
if (null == redissonProperties.getRedisHost() || "".equals(redissonProperties.getRedisHost())) {
return null;
}
config.useSingleServer()
.setAddress(REDIS_PROTOCOL_PREFIX+redissonProperties.getRedisHost() + ":" + redissonProperties.getRedisPort())
.setTimeout(redissonProperties.getTimeout())
.setPassword(redissonProperties.getPassword())
.setConnectionPoolSize(redissonProperties.getConnectionPoolSize())
.setConnectionMinimumIdleSize(redissonProperties.getConnectionMinimumIdleSize());
return config;
}
/**
* 初始化RedissonClient
* 采用实例化顺序:集群模式 > 哨兵模式 > 单机模式
*/
@Bean
RedissonClient redissonSingle() {
Config config = redissonClusterSentinel();
if (null == config) {
config = redissonMasterSentinel();
if (null == config) {
config = redissonAddressSingle();
}
}
if (null != config) {
return Redisson.create(config);
}
return null;
}
/**
* @version 装配RedissonDistributedLockerServiceImpl类,并将实例注入到RedissLockUtil
*/
@Bean
DistributedLockerService distributedLocker(RedissonClient redissonClient) {
RedissonDistributedLockerServiceImpl locker = new RedissonDistributedLockerServiceImpl();
locker.setRedissonClient(redissonClient);
RedissLockUtil.setLocker(locker);
return locker;
}
}
2.4 Redisson实现类和工具类
实现类RedissonDistributedLockerServiceImpl
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
/**
* @version Redisson实现类
*/
@Service
public class RedissonDistributedLockerServiceImpl
implements DistributedLockerService {
private RedissonClient redissonClient;
public void setRedissonClient(RedissonClient redissonClient) {
this.redissonClient = redissonClient;
}
private static final String LOCK = "redisson_lock_";
@Override
public RLock lock(String lockKey) {
RLock lock = redissonClient.getLock(LOCK + lockKey);
lock.lock();
return lock;
}
@Override
public RLock lock(String lockKey, int leaseTime) {
RLock lock = redissonClient.getLock(LOCK + lockKey);
lock.lock(leaseTime, TimeUnit.SECONDS);
return lock;
}
@Override
public RLock lock(String lockKey, TimeUnit unit, int timeout) {
RLock lock = redissonClient.getLock(LOCK + lockKey);
lock.lock(timeout, unit);
return lock;
}
@Override
public boolean tryLock(String lockKey, TimeUnit unit, int waitTime, int leaseTime) {
RLock lock = redissonClient.getLock(LOCK + lockKey);
try {
return lock.tryLock(waitTime, leaseTime, unit);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
}
@Override
public boolean isLocked(String lockKey) {
RLock lock = redissonClient.getLock(LOCK + lockKey);
return lock.isLocked();
}
@Override
public void unlock(String lockKey) {
RLock lock = redissonClient.getLock(LOCK + lockKey);
if (null != lock) {
lock.unlock();
}
}
@Override
public void unlock(RLock lock) {
lock.unlock();
}
}
2.5 Redisson工具类RedissLockUtil
import org.redisson.api.RLock;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
/**
* @desc 工具类
*/
@Component
public class RedissLockUtil {
private static DistributedLockerService distributedLockerService;
public static void setLocker(DistributedLockerService locker) {
distributedLockerService = locker;
}
/**
* 加锁
* @param lockKey
* @return
*/
public static RLock lock(String lockKey) {
return distributedLockerService.lock(lockKey);
}
/**
* 释放锁
* @param lockKey
*/
public static void unlock(String lockKey) {
distributedLockerService.unlock(lockKey);
}
/**
* 释放锁
* @param lock
*/
public static void unlock(RLock lock) {
distributedLockerService.unlock(lock);
}
/**
* 带超时的锁
* @param lockKey
* @param timeout 超时时间 单位:秒
*/
public static RLock lock(String lockKey, int timeout) {
return distributedLockerService.lock(lockKey, timeout);
}
/**
* 带超时的锁
* @param lockKey
* @param unit 时间单位
* @param timeout 超时时间
*/
public static RLock lock(String lockKey, TimeUnit unit, int timeout) {
return distributedLockerService.lock(lockKey, unit, timeout);
}
/**
* 尝试获取锁
* @param lockKey
* @param waitTime 最多等待时间
* @param leaseTime 上锁后自动释放锁时间
* @return
*/
public static boolean tryLock(String lockKey, int waitTime, int leaseTime) {
return distributedLockerService.tryLock(lockKey, TimeUnit.SECONDS, waitTime, leaseTime);
}
public static boolean isLocked(String lockKey) {
return distributedLockerService.isLocked(lockKey);
}
/**
* 尝试获取锁
* @param lockKey
* @param unit 时间单位
* @param waitTime 最多等待时间
* @param leaseTime 上锁后自动释放锁时间
* @return
*/
public static boolean tryLock(String lockKey, TimeUnit unit, int waitTime, int leaseTime) {
return distributedLockerService.tryLock(lockKey, unit, waitTime, leaseTime);
}
}
2.6 方法体内使用锁
方法体内使用锁,锁定某段区间代码 间的业务。例如下述业务:挂起订单
@Transactional(rollbackFor = Exception.class)
public void hangUpOrder(String orderId) {
Order order = orderMapper.findByOrderId(orderId);
.......
try {
//加锁
RedissLockUtil.lock(orderRole.getOrderUuid());
Date time = new Date();
if ("启用".equals(order.getOrderHangUpStatus())) {
// 当前是非挂起状态,进行挂起操作.....
.......
}
} catch (Exception e) {
log.error("挂起操作有误:{}", e.getMessage(), e);
} finally {
//释放锁
RedissLockUtil.unlock(orderRole.getOrderUuid());
}
}
场景一:使用tryLock获取Redis锁。
调用tryLock方法来尝试获取锁。如果成功获得锁,则在持有锁时执行try块中的代码。如果无法获取锁,则执行else块中的代码。
RLock lock = redisson.getLock("myLock");
//lock.tryLock(10, TimeUnit.SECONDS);等待10秒钟,未指定leaseTime参数,
开启看门狗(watchdog)机制自动续约
//lock.tryLock(1L, 10L, TimeUnit.SECONDS);等待1秒钟,指定leaseTime参数10秒(锁从持有到自动释放的时长),watchdog机制不会自动续约
if (lock.tryLock()) {
try {
// Do some work while holding the lock
} finally {
lock.unlock();
}
} else {
// Failed to acquire the lock
}
场景二:使用lock获取Redis锁。
调用lock方法来获取锁。如果不能立即获得锁,代码将阻塞,直到锁可用为止。获取锁后,将执行try块中的代码,同时持有锁。最后,在Finally块中释放锁。
RLock lock = redisson.getLock("myLock");
long leaseTime = 10L;
//指定leaseTime参数10秒(锁从持有到自动释放的时长)
//lock.lock(leaseTime, TimeUnit.SECONDS);
lock.lock();
try {
// Do some work while holding the lock
} finally {
lock.unlock();
}