在某些時候,我們可能想基于字符串做一些事情,比如:針對同一用戶的并發(fā)同步操作,使用鎖字符串的方式實現(xiàn)比較合理。因為只有在相同字符串的情況下,并發(fā)操作才是不被允許的。而如果我們不分青紅皂白直接全部加鎖,那么整體性能就下降得厲害了。
網(wǎng)站建設(shè)哪家好,找創(chuàng)新互聯(lián)!專注于網(wǎng)頁設(shè)計、網(wǎng)站建設(shè)、微信開發(fā)、微信小程序開發(fā)、集團企業(yè)網(wǎng)站建設(shè)等服務(wù)項目。為回饋新老客戶創(chuàng)新互聯(lián)還提供了淅川免費建站歡迎大家使用!
因為string的多樣性,看起來string鎖是天然比分段鎖之類的高級鎖更有優(yōu)勢呢。
因為String 類型的變量賦值是這樣的: String a = "hello world."; 所有往往會有個錯誤的映象,String對象就是不可變的。
額,關(guān)于這個問題的爭論咱們就不細(xì)說了,總之, "a" != "a" 是有可能成立的。
另外,針對上鎖這件事,我們都知道,鎖是要針對同一個對象,才會有意義。所以,粗略的,我們可以這樣使用字符串鎖:
public void method1() { String str1 = "a"; synchronized (str1) { // do sync a things... } } public void method2() { String str2 = "a"; synchronized (str2) { // do sync b things... } }
乍一看,這的確很方便簡單。但是,前面說了, "a" 是可能不等于 "a" 的(這是大部分情況,只有當(dāng)String被存儲在常量池中時值相同的String變量才相等)。
所以,我們可以稍微優(yōu)化下:
public void method3() { String str1 = "a"; synchronized (str1.intern()) { // do sync a things... } } public void method4() { String str2 = "a"; synchronized (str2.intern()) { // do sync b things... } }
看起來還是很方便簡單的,其原理就是把String對象放到常量池中。但是會有個問題,這些常量池的數(shù)據(jù)如何清理呢?
不管怎么樣,我們是不是可以自己去基于String實現(xiàn)一個鎖呢?
肯定是可以的了!直接上代碼!
import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.CountDownLatch; /** * 基于string 的鎖實現(xiàn) */ public final class StringBasedMutexLock { private static final Logger logger = LoggerFactory.getLogger(StringBasedMutexLock.class); /** * 字符鎖 管理器, 將每個字符串 轉(zhuǎn)換為一個 CountDownLatch * * 即鎖只會發(fā)生在真正有并發(fā)更新 同一個 String 的情況下 * */ private static final ConcurrentMap<String, CountDownLatch> lockKeyHolder = new ConcurrentHashMap<>(); /** * 基于lockKey 上鎖,同步執(zhí)行 * * @param lockKey 字符鎖 */ public static void lock(String lockKey) { while (!tryLock(lockKey)) { try { logger.debug("【字符鎖】并發(fā)更新鎖升級, {}", lockKey); blockOnSecondLevelLock(lockKey); } catch (InterruptedException e) { Thread.currentThread().interrupt(); logger.error("【字符鎖】中斷異常:" + lockKey, e); break; } } } /** * 釋放 lockKey 對應(yīng)的鎖選項,使其他線程可執(zhí)行 * * @param lockKey 要使用互斥的字符串 * @return true: 釋放成功, false: 釋放失敗,可能被其他線程誤釋放 */ public static boolean unlock(String lockKey) { // 先刪除鎖,再釋放鎖,此處會導(dǎo)致后續(xù)進(jìn)來的并發(fā)優(yōu)先執(zhí)行,無影響 CountDownLatch realLock = getAndReleaseLock1(lockKey); releaseSecondLevelLock(realLock); return true; } /** * 嘗試給指定字符串上鎖 * * @param lockKey 要使用互斥的字符串 * @return true: 上鎖成功, false: 上鎖失敗 */ private static boolean tryLock(String lockKey) { // 此處會導(dǎo)致大量 ReentrantLock 對象創(chuàng)建嗎? // 其實不會的,這個數(shù)量最大等于外部并發(fā)數(shù),只是對 gc 不太友好,會反復(fù)創(chuàng)建反復(fù)銷毀y return lockKeyHolder.putIfAbsent(lockKey, new CountDownLatch(1)) == null; } /** * 釋放1級鎖(刪除) 并返回重量級鎖 * * @param lockKey 字符鎖 * @return 真正的鎖 */ private static CountDownLatch getAndReleaseLock1(String lockKey) { return lockKeyHolder.remove(lockKey); } /** * 二級鎖鎖定(鎖升級) * * @param lockKey 鎖字符串 * @throws InterruptedException 中斷時拋出異常 */ private static void blockOnSecondLevelLock(String lockKey) throws InterruptedException { CountDownLatch realLock = getRealLockByKey(lockKey); // 為 null 說明此時鎖已被刪除, next race if(realLock != null) { realLock.await(); } } /** * 二級鎖解鎖(如有必要) * * @param realLock 鎖實例 */ private static void releaseSecondLevelLock(CountDownLatch realLock) { realLock.countDown(); } /** * 通過key 獲取對應(yīng)的鎖實例 * * @param lockKey 字符串鎖 * @return 鎖實例 */ private static CountDownLatch getRealLockByKey(String lockKey) { return lockKeyHolder.get(lockKey); } }
使用時,只需傳入 lockKey 即可。
// 加鎖 StringBasedMutexLock.lock(linkKey); // 解鎖 StringBasedMutexLock.unlock(linkKey);
這樣做有什么好處嗎?
1. 使用ConcurrentHashMap實現(xiàn)鎖獲取,性能還是不錯的;
2. 每個字符串對應(yīng)一個鎖,使用完成后就刪除,不會導(dǎo)致內(nèi)存溢出問題;
3. 可以作為一個外部工具使用,業(yè)務(wù)代碼接入方便,無需像 synchronized 一樣,需要整段代碼包裹起來;
不足之處?
1. 使用ConcurrentHashMap實現(xiàn)鎖獲取,性能還是不錯的;
2. 每個字符串對應(yīng)一個鎖,使用完成后就刪除,不會導(dǎo)致內(nèi)存溢出問題;
3. 可以作為一個外部工具使用,業(yè)務(wù)代碼接入方便,無需像 synchronized 一樣,需要整段代碼包裹起來;
4. 本文只是想展示實現(xiàn) String 鎖,此鎖并不適用于分布式場景下的并發(fā)處理;
擴展: 如果不使用 String 做鎖,如何保證大并發(fā)前提下的小概率并發(fā)場景的線程安全?
我們知道 CAS 的效率是比較高的,我們可以使用原子類來進(jìn)行CAS的操作。
比如,我們添加一狀態(tài)字段, 操作此字段以保證線程安全:
/** * 運行狀態(tài) * * 4: 正在刪除, 1: 正在放入隊列中, 0: 正常無運行 */ private transient volatile AtomicInteger runningStatus = new AtomicInteger(0); // 更新時先獲取該狀態(tài): public void method5() { AtomicInteger runningStatus = link.getRunningStatus(); // 正在刪除數(shù)據(jù)過程中,則等待 if(!runningStatus.compareAndSet(0, 1)) { // 1. 等待另外線程刪除完成 // 2. 刪除正在更新標(biāo)識 // 3. 重新運行本次數(shù)據(jù)放入邏輯 long lockStartTime = System.currentTimeMillis(); long maxLockTime = 10 * 1000; while (!runningStatus.compareAndSet(0, 1)) { if(System.currentTimeMillis() - lockStartTime > maxLockTime) { break; } } runningStatus.compareAndSet(1, 0); throw new RuntimeException("數(shù)據(jù)正在更新,重新運行: " + link.getLinkKey() + link); } try { // do sync things } finally { runningStatus.compareAndSet(1, 0); } } public void method6() { AtomicInteger runningStatus = link.getRunningStatus(); if (!runningStatus.compareAndSet(0, 4)) { logger.error(" 數(shù)據(jù)正在更新中,不得刪除,返回 "); return; } try { // do sync things } catch (Exception e) { logger.error("并發(fā)更新異常:", e); } finally { runningStatus.compareAndSet(4, 0); } }
實際測試下來,CAS 性能是要比 synchronized 之類的鎖性能要好的。當(dāng)然,我們這里針對的并發(fā)數(shù)都是極少的,我們只是想要保證這極少情況下的線程安全性。所以,其實也還好。
總結(jié)
以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,謝謝大家對創(chuàng)新互聯(lián)的支持。
名稱欄目:基于String實現(xiàn)同步鎖的方法步驟
當(dāng)前鏈接:http://jinyejixie.com/article6/ipicog.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供用戶體驗、網(wǎng)站設(shè)計公司、品牌網(wǎng)站建設(shè)、移動網(wǎng)站建設(shè)、品牌網(wǎng)站設(shè)計、微信公眾號
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)