(1)同一進程
重入鎖
使用 ReentrantLock
獲取鎖的時候會判斷當前線程是否爲獲取鎖的線程,如果是則將同步的狀態 +1 ,釋放鎖的時候則將狀態 -1。只有將同步狀態的次數置爲 0 的時候纔會最終釋放鎖。
讀寫鎖
使用 ReentrantReadWriteLock
,同時維護一對鎖:讀鎖和寫鎖。當寫線程訪問時則其他所有鎖都將阻塞,讀線程訪問時則不會。通過讀寫鎖的分離可以很大程度的提高併發量和吞吐量。
(2)不同進程(分佈式鎖)
基於數據庫
可以創建一張表,將其中的某個字段設置爲唯一索引
,當多個請求過來的時候只有新建記錄成功的請求才算獲取到鎖,當使用完畢刪除這條記錄的時候即釋放鎖。
存在的問題:
- 數據庫單點問題,掛了怎麼辦?
- 不是重入鎖,同一進程無法在釋放鎖之前再次獲得鎖,因爲數據庫中已經存在了一條記錄了。
- 鎖是非阻塞的,一旦
insert
失敗則會立即返回,並不會進入阻塞隊列只能下一次再次獲取。 - 鎖沒有失效時間,如果那個進程解鎖失敗那就沒有請求可以再次獲取鎖了。
解決方案:
- 數據庫切換爲主從,不存在單點。
- 在表中加入一個同步狀態字段,每次獲取鎖的是加 1 ,釋放鎖的時候
-1
,當狀態爲 0 的時候就刪除這條記錄,即釋放鎖。 - 非阻塞的情況可以用
while
循環來實現,循環的時候記錄時間,達到 X 秒記爲超時,break
。 - 可以開啓一個定時任務每隔一段時間掃描找出多少 X 秒都沒有被刪除的記錄,主動刪除這條記錄。
基於 Redis
使用 setNX(key) setEX(timeout)
命令,只有在該 key
不存在的時候創建這個 key
,就相當於獲取了鎖。由於有超時時間,所以過了規定時間會自動刪除,這樣也可以避免死鎖。
可以參考: