文章目錄
- 前言
- 一、RedissonLock源代碼分析
- 1.1 嘗試加鎖
- 2.2 解鎖
- 二、鎖業務應用
- 1.服務層方法@注解方式 注入鎖
- 1.1 定義DistributedLock @注解類
- 1.2 定義DistributedLockAspect 切片類
- 1.3 嘗試獲取鎖代碼片斷
- 1.4 釋放鎖代碼片斷
- 1.5 服務層注入鎖@注解
- 2.代碼行加鎖
- 2.1 pom.xml文件引入redisson、redis
- 2.2 配置RedissonProperties屬性類
- 2.3 Redisson裝配類
- 2.4 Redisson實現類和工具類
- 2.5 Redisson工具類RedissLockUtil
- 2.6 方法體內使用鎖
前言
通常服務端存在定時器業務時,如果分布式部署,多臺同時執行會出現重復的數據,為了避免這種情況,可采用分布式鎖。本章節講解分布式鎖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 {......//創建RedisPubSubListenerRedisPubSubListener<Object> listener = this.createListener(channelName,value);//訂閱通道channelName、信號semaphore,綁定RedisPubSubListenerthis.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-申請鎖,申請鎖并返回RFutureRFuture<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()),表示分布式鎖的keyARGV[1]就是internalLockLeaseTime,即鎖的租約時間(持有鎖的有效時間),默認30sARGV[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 {@Resourceprivate 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();//解析keylog.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 {@Resourceprivate 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* 采用實例化順序:集群模式 > 哨兵模式 > 單機模式*/@BeanRedissonClient 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*/@BeanDistributedLockerService 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_";@Overridepublic RLock lock(String lockKey) {RLock lock = redissonClient.getLock(LOCK + lockKey);lock.lock();return lock;}@Overridepublic RLock lock(String lockKey, int leaseTime) {RLock lock = redissonClient.getLock(LOCK + lockKey);lock.lock(leaseTime, TimeUnit.SECONDS);return lock;}@Overridepublic RLock lock(String lockKey, TimeUnit unit, int timeout) {RLock lock = redissonClient.getLock(LOCK + lockKey);lock.lock(timeout, unit);return lock;}@Overridepublic 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;}}@Overridepublic boolean isLocked(String lockKey) {RLock lock = redissonClient.getLock(LOCK + lockKey);return lock.isLocked();}@Overridepublic void unlock(String lockKey) {RLock lock = redissonClient.getLock(LOCK + lockKey);if (null != lock) {lock.unlock();}}@Overridepublic 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();
}