老大吩咐的可重入分散式鎖,終於完美的實現了!!!
阿新 • • 發佈:2020-06-15
## 重做永遠比改造簡單
最近在做一個專案,將一個其他公司的實現系統(*下文稱作舊系統*),完整的整合到自己公司的系統(*下文稱作新系統*)中,這其中需要將對方實現的功能完整在自己系統也實現一遍。
舊系統還有一批存量商戶,為了不影響存量商戶的體驗,新系統提供的對外介面,還必須得跟以前一致。最後系統完整切換之後,功能只執行在新系統中,這就要求舊系統的資料還需要完整的遷移到新系統中。
當然這些在做這個專案之前就有預期,想過這個過程很難,但是沒想到有那麼難。原本感覺排期大半年,時間還是挺寬裕,現在感覺就是大坑,還不得不在坑裡一點點去填。
![](https://img2020.cnblogs.com/other/1419561/202006/1419561-20200615070959849-950502677.jpg)
哎,說多都是淚,不吐槽了,等到下次做完再給大家覆盤下真正心得體會。
回到正文,上篇文章[Redis 分散式鎖](https://mp.weixin.qq.com/s/HlD46m-OP-HDdKJFxgqFYA),咱們基於 Redis 實現一個分散式鎖。這個分散式鎖基本功能沒什麼問題,但是缺少可重入的特性,所以這篇文章小黑哥就帶大家來實現一下可重入的分散式鎖。
本篇文章將會涉及以下內容:
- 可重入
- 基於 ThreadLocal 實現方案
- 基於 Redis Hash 實現方案
> 先贊後看,養成習慣。微信搜尋「程式通事」,關注就完事了~
## 可重入
說到可重入鎖,首先我們來看看一段來自 [wiki](https://zh.wikipedia.org/wiki/%E5%8F%AF%E9%87%8D%E5%85%A5) 上可重入的解釋:
>若一個程式或子程式可以“在任意時刻被中斷然後作業系統排程執行另外一段程式碼,這段程式碼又呼叫了該子程式不會出錯”,則稱其為**可重入**(reentrant或re-entrant)的。即當該子程式正在執行時,執行執行緒可以再次進入並執行它,仍然獲得符合設計時預期的結果。與多執行緒併發執行的執行緒安全不同,可重入強調對單個執行緒執行時重新進入同一個子程式仍然是安全的。
當一個執行緒執行一段程式碼成功獲取鎖之後,繼續執行時,又遇到加鎖的程式碼,可重入性就就保證執行緒能繼續執行,而不可重入就是需要等待鎖釋放之後,再次獲取鎖成功,才能繼續往下執行。
用一段 Java 程式碼解釋可重入:
```java
public synchronized void a() {
b();
}
public synchronized void b() {
// pass
}
```
假設 X 執行緒在 a 方法獲取鎖之後,繼續執行 b 方法,如果此時**不可重入**,執行緒就必須等待鎖釋放,再次爭搶鎖。
鎖明明是被 X 執行緒擁有,卻還需要等待自己釋放鎖,然後再去搶鎖,這看起來就很奇怪,我釋放我自己~
![我打我自己](https://img2020.cnblogs.com/other/1419561/202006/1419561-20200615071000003-1637972343.gif)
可重入性就可以解決這個尷尬的問題,當執行緒擁有鎖之後,往後再遇到加鎖方法,直接將加鎖次數加 1,然後再執行方法邏輯。退出加鎖方法之後,加鎖次數再減 1,當加鎖次數為 0 時,鎖才被真正的釋放。
可以看到可重入鎖最大特性就是計數,計算加鎖的次數。所以當可重入鎖需要在分散式環境實現時,我們也就需要統計加鎖次數。
分散式可重入鎖實現方式有兩種:
- 基於 ThreadLocal 實現方案
- 基於 Redis Hash 實現方案
首先我們看下基於 ThreadLocal 實現方案。
## 基於 ThreadLocal 實現方案
### 實現方式
Java 中 `ThreadLocal`可以使每個執行緒擁有自己的例項副本,我們可以利用這個特性對執行緒重入次數進行技術。
下面我們定義一個`ThreadLocal`的全域性變數 `LOCKS`,記憶體儲存 `Map` 例項變數。
```javascript
private static ThreadLocal