摘要:鎖的部分細節(jié)不同場景鎖的表現(xiàn)不同獨占共享讀寫分布式鎖的簡單實現(xiàn)分布式鎖實現(xiàn)的三個核心要素加鎖最簡單的方法是使用命令。嘗試獲取分布式鎖客戶端鎖線程超期時間是否獲取成功釋放分布式鎖客戶端鎖請求標識是否釋放成功
鎖的由來:
多線程環(huán)境中,經(jīng)常遇到多個線程訪問同一個 共享資源 ,這時候作為開發(fā)者必須考慮如何維護數(shù)據(jù)一致性,這就需要某種機制來保證只有滿足某個條件(獲取鎖成功)的線程才能訪問資源,而不滿足條件(獲取鎖失?。┑木€程只能等待,在下一輪競爭中來獲取鎖才能訪問資源。
兩個知識點:1.高級緩存Cache
CPU為了提高處理速度,不和內存直接進行交互,而是使用Cache。
可能引發(fā)的問題:
如果多個處理器同時對共享變量進行讀改寫操作 (i++就是經(jīng)典的讀改寫操作),那么共享變量就會被多個處理器同時進行操作,這樣讀改寫操作就不是原子的了,操作完之后共享變量的值會和期望的不一致。
造成此結果的原因:
多個處理器同時從各自的緩存中讀取變量i,分別進行加1操作,然后分別寫入 系統(tǒng)內存中。
處理器層面的解決方案:
處理器使用總線鎖就是來解決這個問題的。所謂總線鎖就是使用處理器提供的一個 LOCK#信號,當一個處理器在總線上輸出此信號時,其他處理器的請求將被阻塞住,那么該處理器可以獨占共享內存。
2.CAS(Compare And Swap)+volatile
CAS 操作包含三個操作數(shù) —— 內存位置(V)、預期原值(A)和新值(B)。執(zhí)行CAS操作的時候,將內存位置的值與預期原值比較,如果相匹配,那么處理器會自動將該位置值更新為新值。否則,處理器不做任何操作。
java的Atomic以及一些它自帶的類中的cas操作都是通過借助cmpxchg指令完成的。他保證同一時刻只能有一個線程cas成功。
舉個例子
以AtomicIneger的源碼為例來看看CAS操作:
for(;;)表示循環(huán),只有當if判斷為true才退出。而if判斷的內容就是是否CAS成功。
volatile的作用:
1)將當前處理器緩存行的數(shù)據(jù)寫回到系統(tǒng)內存。
2)這個寫回內存的操作會使在其他CPU里緩存了該內存地址的數(shù)據(jù)無效。
循環(huán)CAS+volatile是實現(xiàn)鎖的關鍵。
Lock鎖的部分細節(jié)不同場景鎖的表現(xiàn)不同:獨占?共享?讀寫?
分布式鎖(redis的簡單實現(xiàn))分布式鎖實現(xiàn)的三個核心要素:
1.加鎖
最簡單的方法是使用setnx命令。key是鎖的唯一標識,按業(yè)務來決定命名。比如想要給一種商品的秒殺活動加鎖,可以給key命名為 “l(fā)ock_sale_商品ID” 。而value設置成什么呢?我們可以姑且設置成1。加鎖的偽代碼如下:
setnx(key,1)
SETNX key value
將 key 的值設為 value ,當且僅當 key 不存在。
若給定的 key 已經(jīng)存在,則 SETNX 不做任何動作。
SETNX 是『SET if Not eXists』(如果不存在,則 SET)的簡寫。 時間復雜度: O(1) 返回值: 設置成功,返回 1 。 設置失敗,返回 0 。
當一個線程執(zhí)行setnx返回1,說明key原本不存在,該線程成功得到了鎖;當一個線程執(zhí)行setnx返回0,說明key已經(jīng)存在,該線程搶鎖失敗。
2.解鎖
有加鎖就得有解鎖。當?shù)玫芥i的線程執(zhí)行完任務,需要釋放鎖,以便其他線程可以進入。釋放鎖的最簡單方式是執(zhí)行del指令,偽代碼如下:
del(key)
釋放鎖之后,其他線程就可以繼續(xù)執(zhí)行setnx命令來獲得鎖。
3.設置超時時間
如果一個得到鎖的線程在執(zhí)行任務的過程中掛掉,來不及顯式地釋放鎖,這塊資源將會永遠被鎖住,別的線程再也別想進來。
所以,setnx的key必須設置一個超時時間,以保證即使沒有被顯式釋放,這把鎖也要在一定時間后自動釋放。setnx不支持超時參數(shù),所以需要額外的指令,偽代碼如下:
expire(key, 30)
綜合起來,我們分布式鎖實現(xiàn)的第一版?zhèn)未a如下:
if(setnx(key,1) == 1){ expire(key,30) do something ...... del(key) }
上述代碼的問題:
1 setnx和expire的非原子性
setnx剛執(zhí)行成功,還未來得及執(zhí)行expire指令,節(jié)點1 Duang的一聲掛掉了。
這樣一來,這個鎖就長生不死了。
解決方案:
Redis 2.6.12以上版本為set指令增加了可選參數(shù),偽代碼如下:
set(key,1,30,NX)
2 del 導致誤刪
可以在del釋放鎖之前做一個判斷,驗證當前的鎖是不是自己加的鎖
至于具體的實現(xiàn),可以在加鎖的時候把當前的線程ID當做value,并在刪除之前驗證key對應的value是不是自己線程的ID。
加鎖:
String threadId = Thread.currentThread().getId() set(key,threadId ,30,NX)
解鎖:
if(threadId .equals(redisClient.get(key))){ del(key) }
這樣做又隱含了一個新的問題,判斷和釋放鎖是兩個獨立操作,不是原子性。
這一塊要用Lua腳本來實現(xiàn):
String luaScript = "if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("del", KEYS[1]) else return 0 end"; redisClient.eval(luaScript , Collections.singletonList(key), Collections.singletonList(threadId));
redis官方說:eval命令在執(zhí)行l(wèi)ua腳本時會當作一個命令去執(zhí)行,并且直到命令執(zhí)行完成redis才會去執(zhí)行其他命令,所以就變成了一個原子操作。
3出現(xiàn)并發(fā)的可能性
進程1在超時時間內未執(zhí)行完代碼,此時進程2是可以獲取鎖的,會出現(xiàn)兩個進程同時訪問一個資源的情況。
解決方案:可以在進程1所在的jvm環(huán)境中開一個線程專門用來“續(xù)命”,當需要解鎖的時候,通知這個續(xù)命線程結束執(zhí)行。
private static final String LOCK_SUCCESS = "OK"; private static final String SET_IF_NOT_EXIST = "NX"; private static final String SET_WITH_EXPIRE_TIME = "PX"; /** * 嘗試獲取分布式鎖 * @param jedis Redis客戶端 * @param lockKey 鎖 * @param requestId 線程Id * @param expireTime 超期時間 * @return 是否獲取成功 */ public static boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) { String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime); if (LOCK_SUCCESS.equals(result)) { return true; } return false; }
private static final Long RELEASE_SUCCESS = 1L; /** * 釋放分布式鎖 * @param jedis Redis客戶端 * @param lockKey 鎖 * @param requestId 請求標識 * @return 是否釋放成功 */ public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) { String script = "if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("del", KEYS[1]) else return 0 end"; Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId)); if (RELEASE_SUCCESS.equals(result)) { return true; } return false; }
文章版權歸作者所有,未經(jīng)允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/72228.html
摘要:否則數(shù)據(jù)會出現(xiàn)不同步問題我使用的做分布式鎖管理,用注解事務管理。但是出現(xiàn)另外一個問題,鎖超時但是事務仍未提交。 最近開發(fā)一個小程序遇到一個需求需要實現(xiàn)分布式事務管理 業(yè)務需求 用戶在使用小程序的過程中可以查看景點,對景點地區(qū)或者城市標記是否想去,那么需要統(tǒng)計一個地點被標記的人數(shù),以及記錄某個用戶對某個地點是否標記為想去,用兩個表存儲數(shù)據(jù),一個地點表記錄改地點被標記的次數(shù),一個用戶意向表...
摘要:分布式鎖基于實現(xiàn)分布式鎖思考幾個問題鎖為什么不能應用于分布式鎖雖然能夠解決同步問題,但是每次只有一個線程訪問,并且鎖屬于鎖,僅適用于單點部署然而分布式需要部署多臺實例,屬于不同的線程對象使用中實現(xiàn)分布式鎖。分布式鎖基于redis實現(xiàn)分布式鎖思考幾個問題?synchronized鎖為什么不能應用于分布式鎖?synchronized雖然能夠解決同步問題,但是每次只有一個線程訪問,并且synchr...
摘要:不過比較膚淺,為了進一步加深對的認識,我利用空閑時間編寫了本篇文章對應的基于的分布式鎖實現(xiàn)。不過我所編寫的分布式鎖還是比較簡陋的,實現(xiàn)的也不夠優(yōu)美,僅僅是個練習,僅供參考使用。好了,題外話就說到這里,接下來我們就來聊聊基于的分布式鎖實現(xiàn)。 1. 背景 最近在學習 Zookeeper,在剛開始接觸 Zookeeper 的時候,完全不知道 Zookeeper 有什么用。且很多資料都是將 Z...
閱讀 2289·2021-11-24 09:38
閱讀 1986·2021-11-22 14:44
閱讀 1150·2021-07-29 13:48
閱讀 2615·2019-08-29 13:20
閱讀 1115·2019-08-29 11:08
閱讀 2046·2019-08-26 10:58
閱讀 1264·2019-08-26 10:55
閱讀 3149·2019-08-26 10:39