Java 中通過 key 獲取鎖的方法
一、概覽
本文我們將了解如何通過特定鍵獲取鎖,以保證該鍵上的操作的線程安全,并且不妨礙其他鍵。
一般來說,我們需要實現(xiàn)兩個方法:
void lock(String key) void unlock(String key)
本文以字符串作為鍵為例,大家可以根據(jù)實際需要改造成任意類型的鍵,重寫 equas 和 hashCode 方法,保證唯一性即可。
二、簡單的互斥鎖
假設需要滿足當前線程獲取鎖則需要執(zhí)行特定代碼,否則不執(zhí)行這個場景。
我們可以維護一系列 Key 的 Set, 在使用時添加到 Set 中,解鎖時移除對應的 Key。
此時,需要考慮線程安全問題。因此需要使用線程安全的 Set 實現(xiàn),如基于 ConcurrentHashMap 的線程安全 Set。
public class SimpleExclusiveLockByKey { private static Set<String> usedKeys= ConcurrentHashMap.newKeySet(); public boolean tryLock(String key) { return usedKeys.add(key); } public void unlock(String key) { usedKeys.remove(key); } }
使用案例:
String key = "key"; SimpleExclusiveLockByKey lockByKey = new SimpleExclusiveLockByKey(); try { lockByKey.tryLock(key); // 在這里添加對該 key 獲取鎖之后要執(zhí)行的代碼 } finally { // 非常關鍵 lockByKey.unlock(key); }
注意一定要在 finally 代碼塊中解鎖,以保證即便發(fā)生異常時,也可以正常解鎖。
三、按鍵來獲取和釋放鎖
以上代碼可以保證獲取鎖后才執(zhí)行,但無法實現(xiàn)未拿到鎖的線程等待的效果。
有時候,我們需要讓未獲取到對應鎖的線程等待。
流程如下:
- 第一個線程獲取某個 key 的鎖
- 第二個線程獲取同一個 key 的鎖,第二個線程需要等待
- 第一個線程釋放某個 key 的鎖
- 第二個線程獲取該 key 的鎖,然后執(zhí)行其代碼
3.1 使用線程計數(shù)器定義 Lock
我們可以使用 ReentrantLock 來實行線程阻塞。
我們通過內(nèi)部類來封裝 Lock。該類統(tǒng)計某個 key 上執(zhí)行的線程數(shù)。暴露兩個方法,一個是線程數(shù)增加,一個是減少線程數(shù)。
private static class LockWrapper { private final Lock lock = new ReentrantLock(); private final AtomicInteger numberOfThreadsInQueue = new AtomicInteger(1); private LockWrapper addThreadInQueue() { numberOfThreadsInQueue.incrementAndGet(); return this; } private int removeThreadFromQueue() { return numberOfThreadsInQueue.decrementAndGet(); } }
3.2 處理排隊的線程
接下來繼續(xù)使用 ConcurrentHashMap , key 作為鍵, LockWrapper 作為值。
保證同一個 key 使用同一個 LockWrapper 中的同一把鎖。
private static ConcurrentHashMap<String, LockWrapper> locks = new ConcurrentHashMap<String, LockWrapper>();
一個線程想要獲取某個 key 的鎖時,需要看該 key 對應的 LockWrapper 是否已經(jīng)存在。
- 如果不存在,創(chuàng)建一個 LockWrapper ,計數(shù)器設置為1
- 如果存在,對應的 LockWrapper 加1
public void lock(String key) { LockWrapper lockWrapper = locks.compute(key, (k, v) -> v == null ? new LockWrapper() : v.addThreadInQueue()); lockWrapper.lock.lock(); }
3.3 解鎖和移除 Entry
解鎖時將等待的隊列減一。
當前 key 對應的線程數(shù)為 0 時,可以將其從 ConcurrentHashMap 中移除。
public void unlock(String key) { LockWrapper lockWrapper = locks.get(key); lockWrapper.lock.unlock(); if (lockWrapper.removeThreadFromQueue() == 0) { // NB : We pass in the specific value to remove to handle the case where another thread would queue right before the removal locks.remove(key, lockWrapper); } }
3.4 總結
最終效果如下:
public class LockByKey { private static class LockWrapper { private final Lock lock = new ReentrantLock(); private final AtomicInteger numberOfThreadsInQueue = new AtomicInteger(1); private LockWrapper addThreadInQueue() { numberOfThreadsInQueue.incrementAndGet(); return this; } private int removeThreadFromQueue() { return numberOfThreadsInQueue.decrementAndGet(); } } private static ConcurrentHashMap<String, LockWrapper> locks = new ConcurrentHashMap<String, LockWrapper>(); public void lock(String key) { LockWrapper lockWrapper = locks.compute(key, (k, v) -> v == null ? new LockWrapper() : v.addThreadInQueue()); lockWrapper.lock.lock(); } public void unlock(String key) { LockWrapper lockWrapper = locks.get(key); lockWrapper.lock.unlock(); if (lockWrapper.removeThreadFromQueue() == 0) { // NB : We pass in the specific value to remove to handle the case where another thread would queue right before the removal locks.remove(key, lockWrapper); } } }
使用示例:
String key = "key"; LockByKey lockByKey = new LockByKey(); try { lockByKey.lock(key); // insert your code here } finally { // CRUCIAL lockByKey.unlock(key); }
四、允許同一個 key 同時多個線程運行
我們還需要考慮另外一種場景: 前面對于同一個 key 同一時刻只允許一個線程執(zhí)行。如果我們想實現(xiàn),對于同一個 key ,允許同時運行 n 個線程該怎么辦?
為了方便理解,我們假設同一個 key 允許兩個線程。
- 第一個線程想要獲取 某個 key 的鎖,允許
- 第二個線程也想要獲取該 key 的鎖,允許
- 第三個線程也想獲取該 key 的鎖,該線程需要等待第一個或第二個線程釋放鎖之后才可以執(zhí)行
Semaphore 很適合這種場景。Semaphore 可以控制同時運行的線程數(shù)。
public class SimultaneousEntriesLockByKey { private static final int ALLOWED_THREADS = 2; private static ConcurrentHashMap<String, Semaphore> semaphores = new ConcurrentHashMap<String, Semaphore>(); public void lock(String key) { Semaphore semaphore = semaphores.compute(key, (k, v) -> v == null ? new Semaphore(ALLOWED_THREADS) : v); semaphore.acquireUninterruptibly(); } public void unlock(String key) { Semaphore semaphore = semaphores.get(key); semaphore.release(); if (semaphore.availablePermits() == ALLOWED_THREADS) { semaphores.remove(key, semaphore); } } }
使用案例:
String key = "key"; SimultaneousEntriesLockByKey lockByKey = new SimultaneousEntriesLockByKey(); try { lockByKey.lock(key); // 在這里添加對該 key 獲取鎖之后要執(zhí)行的代碼 } finally { // 非常關鍵 lockByKey.unlock(key); }
五、結論
本文演示如何對某個 key 加鎖,以保證對該 key 的并發(fā)操作限制,可以實現(xiàn)同一個 key 一個或者多個線程同時執(zhí)行。
相關代碼:https://github.com/eugenp/tutorials/tree/master/core-java-modules/core-java-concurrency-advanced-4
原文:https://www.baeldung.com/java-acquire-lock-by-key
到此這篇關于Java 中通過 key 獲取鎖的文章就介紹到這了,更多相關java 獲取鎖內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Android設備如何保證數(shù)據(jù)同步寫入磁盤的實現(xiàn)
這篇文章主要介紹了Android設備如何保證數(shù)據(jù)同步寫入磁盤的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2019-09-09Spring Boot/Angular整合Keycloak實現(xiàn)單點登錄功能
Keycloak新的發(fā)行版命名為Quarkus,專為GraalVM和OpenJDK HotSpot量身定制的一個Kurbernetes Native Java框架,計劃2019年底正式發(fā)布。這篇文章主要介紹了Spring Boot/Angular整合Keycloak實現(xiàn)單點登錄,需要的朋友可以參考下2019-10-10springboot嵌套子類使用方式—前端與后臺開發(fā)的注意事項
這篇文章主要介紹了springboot嵌套子類使用方式—前端與后臺開發(fā)的注意事項,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-03-03TransactionSynchronization的invokeAfterCompletion事務源碼解析
這篇文章主要為大家介紹了TransactionSynchronization的invokeAfterCompletion事務源碼解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-09-09Intellij IDEA 2017新特性之Spring Boot相關特征介紹
Intellij IDEA 2017.2.2版本針對Springboot設置了一些特性,本篇文章給大家簡單介紹一下如何使用這些特性,需要的朋友參考下吧2018-01-01