添加 Maven 依賴java
<dependency> <groupId>org.redisson</groupId> <artifactId>redisson</artifactId> <version>3.12.0</version> </dependency>
添加配置類redis
@Configuration public class MyRedissonConfig { @Bean(destroyMethod = "shutdown") RedissonClient redisson() throws IOException { Config config = new Config(); config.useSingleServer().setAddress("redis://192.168.56.10:6379"); return Redisson.create(config); } }
基本使用代碼以下:app
@GetMapping("/hello") @ResponseBody public String hello() { //獲取Lock鎖,設置鎖的名稱 RLock lock = redisson.getLock("my-lock"); //開啓 lock.lock(); try { System.out.println("上鎖:" + Thread.currentThread().getId()); //模擬業務處理20秒 TimeUnit.SECONDS.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); }finally { System.out.println("解鎖:" + Thread.currentThread().getId()); //釋放 lock.unlock(); } return "hello"; }
當咱們發送 /hello
請求後等待 20 秒獲得響應結果,會在 Redis 中存儲鎖的信息(以下圖所示),期間,其它用戶發送 /hello
請求時會被阻塞,只有前一個請求結束後釋放鎖,當前請求才會進入。ide
思考1:若是在業務處理過程當中程序忽然終止,鎖沒有獲得釋放,是否會一直阻塞下去?測試
通過實驗,在業務處理的20秒中,將服務手動中止,刷新 Redis 中 my-lock 的信息,發現 TTL 不斷的減少,直到失效,發送其它請求可以正常執行,這說明,即便不釋放鎖,Redis 設置的過時時間到了也會自動刪除鎖的信息。ui
//獲取當前線程id long threadId = Thread.currentThread().getId(); //獲取此線程的鎖 Long ttl = tryAcquire(leaseTime, unit, threadId); //若是獲取不到,則說明鎖已經釋放了,直接返回 if (ttl == null) { return; } while (true) { ttl = tryAcquire(leaseTime, unit, threadId); //判斷是否能獲取到鎖 if (ttl == null) { break; } ... }
思考2:過時時間是多少?若是咱們的業務處理時間超過了過時時間,豈不是還沒處理完就把鎖的信息給刪了?this
正常啓動服務訪問 /hello
,刷新 my-lock 的信息,咱們發現,TTL 每次減小到 20 就再次變爲 30,直到業務處理完成,my-lock 被刪除。查找相關源代碼以下:線程
while (true) { //嘗試獲取鎖 ttl = tryAcquire(leaseTime, unit, threadId); //若是獲取不到,說明執行該線程執行結束,就終止循環 if (ttl == null) { break; } //若是獲取到了就繼續循環 if (ttl >= 0) { try { future.getNow().getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { if (interruptibly) { throw e; } future.getNow().getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS); } } else { if (interruptibly) { future.getNow().getLatch().acquire(); } else { future.getNow().getLatch().acquireUninterruptibly(); } } }
繼續深刻源碼能夠看到,若是不指定鎖的時間,就默認爲 30 秒,它有一個好聽的名字:看門狗code
private long lockWatchdogTimeout = 30 * 1000;
只要佔領鎖,就會啓動一個定時任務:每隔一段時間從新給鎖設置過時時間xml
protected RFuture<Boolean> renewExpirationAsync(long threadId) { return commandExecutor.evalWriteAsync(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.<Object>singletonList(getName()), internalLockLeaseTime, getLockName(threadId)); //internalLockLeaseTime就是看門狗的時間 }
每隔多長久刷新一下呢?
//獲取看門狗的時間,賦值給本身 this.internalLockLeaseTime = xxx.getLockWatchdogTimeout(); public long getLockWatchdogTimeout() { return lockWatchdogTimeout; } Timeout task = commandExecutor.getConnectionManager().newTimeout(new TimerTask() { @Override public void run(Timeout timeout) throws Exception { ... } //使用的時候除3,也就是10秒刷新一次 }, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);
思考三:如何自定義過時時間?
lock() 方法還有一個重載方法,能夠傳入過時時間和單位
void lock(long leaseTime, TimeUnit unit);
咱們將以前的代碼修改,設置爲 15 秒,重啓服務再測試
lock.lock(15, TimeUnit.SECONDS);
訪問 /hello
,刷新 Redis 中 my-lock 的信息會發現,TTL 從 15 減到 0,而後鎖信息過時,並不會出現以前的 10秒一刷新,查看源碼會發現:
private <T> RFuture<Long> tryAcquireAsync(long leaseTime, TimeUnit unit, long threadId) { //若是傳入了過時時間,則直接執行tryLockInnerAsync裏面的Lua腳本 if (leaseTime != -1) { return tryLockInnerAsync(leaseTime, unit, threadId, RedisCommands.EVAL_LONG); } //沒有傳入過時時間,執行下面的邏輯 RFuture<Long> ttlRemainingFuture = tryLockInnerAsync(commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(), TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG); ttlRemainingFuture.onComplete((ttlRemaining, e) -> { //有異常,直接返回 if (e != null) { return; } if (ttlRemaining == null) { //刷新過時時間 scheduleExpirationRenewal(threadId); } }); return ttlRemainingFuture; }
一、lock 鎖是線程阻塞的
二、使用 lock 的無參方法,鎖的默認時間是 30 秒,而且會每隔 10 秒刷新爲 30 秒,只要業務沒執行完,就會一直續期,若是執行完成或者忽然停止,則不會再續期,達到過時時間就釋放鎖
三、使用 lock 的有參方法指定時間,到達指定時間會自動解鎖,所以設置的時間必須大於業務執行時間,不然,業務沒執行完,鎖就會被釋放
四、推薦使用指定時間的方式,省掉了續期操做,但須要合理設置過時時間,不能過早的使鎖釋放