日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

锁和分布式锁

發(fā)布時間:2025/5/22 编程问答 17 豆豆
生活随笔 收集整理的這篇文章主要介紹了 锁和分布式锁 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

2019獨角獸企業(yè)重金招聘Python工程師標(biāo)準(zhǔn)>>>

鎖的由來?:

多線程環(huán)境中,經(jīng)常遇到多個線程訪問同一個 共享資源 ,這時候作為開發(fā)者必須考慮如何維護數(shù)據(jù)一致性,這就需要某種機制來保證只有滿足某個條件(獲取鎖成功)的線程才能訪問資源,而不滿足條件(獲取鎖失敗)的線程只能等待,在下一輪競爭中來獲取鎖才能訪問資源。

兩個知識點:

1.高級緩存Cache

?

?

?

?

?

CPU為了提高處理速度,不和內(nèi)存直接進行交互,而是使用Cache。

可能引發(fā)的問題:

?

?

?

?

?

如果多個處理器同時對共享變量進行讀改寫操作 (i++就是經(jīng)典的讀改寫操作),那么共享變量就會被多個處理器同時進行操作,這樣讀改寫操作就不是原子的了,操作完之后共享變量的值會和期望的不一致。

造成此結(jié)果的原因:

多個處理器同時從各自的緩存中讀取變量i,分別進行加1操作,然后分別寫入 系統(tǒng)內(nèi)存中。

處理器層面的解決方案:

處理器使用總線鎖就是來解決這個問題的。所謂總線鎖就是使用處理器提供的一個 LOCK#信號,當(dāng)一個處理器在總線上輸出此信號時,其他處理器的請求將被阻塞住,那么該處理器可以獨占共享內(nèi)存。

2.CAS(Compare And Swap)+volatile

CAS 操作包含三個操作數(shù) —— 內(nèi)存位置(V)、預(yù)期原值(A)和新值(B)。執(zhí)行CAS操作的時候,將內(nèi)存位置的值與預(yù)期原值比較,如果相匹配,那么處理器會自動將該位置值更新為新值。否則,處理器不做任何操作。

java的Atomic以及一些它自帶的類中的cas操作都是通過借助cmpxchg指令完成的。他保證同一時刻只能有一個線程cas成功。

舉個例子

以AtomicIneger的源碼為例來看看CAS操作:

?

?

?

?

?

for(;;)表示循環(huán),只有當(dāng)if判斷為true才退出。而if判斷的內(nèi)容就是是否CAS成功。

?

?

?

?

?

?

?

?

?

?

volatile的作用:

1)將當(dāng)前處理器緩存行的數(shù)據(jù)寫回到系統(tǒng)內(nèi)存。

2)這個寫回內(nèi)存的操作會使在其他CPU里緩存了該內(nèi)存地址的數(shù)據(jù)無效。

循環(huán)CAS+volatile是實現(xiàn)鎖的關(guān)鍵。

Lock鎖的部分細節(jié)

?

?

?

?

?

?

?

?

?

?

不同場景鎖的表現(xiàn)不同:獨占?共享?讀寫?

?

?

?

?

?

分布式鎖(redis的簡單實現(xiàn))

分布式鎖實現(xiàn)的三個核心要素:

  • 1.加鎖

最簡單的方法是使用setnx命令。key是鎖的唯一標(biāo)識,按業(yè)務(wù)來決定命名。比如想要給一種商品的秒殺活動加鎖,可以給key命名為 “l(fā)ock_sale_商品ID” 。而value設(shè)置成什么呢?我們可以姑且設(shè)置成1。加鎖的偽代碼如下:

setnx(key,1)?

SETNX key value

將 key 的值設(shè)為 value ,當(dāng)且僅當(dāng) key 不存在。

若給定的 key 已經(jīng)存在,則 SETNX 不做任何動作。

SETNX 是『SET if Not eXists』(如果不存在,則 SET)的簡寫。 時間復(fù)雜度: O(1) 返回值: 設(shè)置成功,返回 1 。 設(shè)置失敗,返回 0 。

當(dāng)一個線程執(zhí)行setnx返回1,說明key原本不存在,該線程成功得到了鎖;當(dāng)一個線程執(zhí)行setnx返回0,說明key已經(jīng)存在,該線程搶鎖失敗。

  • 2.解鎖

有加鎖就得有解鎖。當(dāng)?shù)玫芥i的線程執(zhí)行完任務(wù),需要釋放鎖,以便其他線程可以進入。釋放鎖的最簡單方式是執(zhí)行del指令,偽代碼如下:

del(key)

釋放鎖之后,其他線程就可以繼續(xù)執(zhí)行setnx命令來獲得鎖。

  • 3.設(shè)置超時時間

如果一個得到鎖的線程在執(zhí)行任務(wù)的過程中掛掉,來不及顯式地釋放鎖,這塊資源將會永遠被鎖住,別的線程再也別想進來。

所以,setnx的key必須設(shè)置一個超時時間,以保證即使沒有被顯式釋放,這把鎖也要在一定時間后自動釋放。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 導(dǎo)致誤刪

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

可以在del釋放鎖之前做一個判斷,驗證當(dāng)前的鎖是不是自己加的鎖

至于具體的實現(xiàn),可以在加鎖的時候把當(dāng)前的線程ID當(dāng)做value,并在刪除之前驗證key對應(yīng)的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腳本時會當(dāng)作一個命令去執(zhí)行,并且直到命令執(zhí)行完成redis才會去執(zhí)行其他命令,所以就變成了一個原子操作。

  • 3出現(xiàn)并發(fā)的可能性

進程1在超時時間內(nèi)未執(zhí)行完代碼,此時進程2是可以獲取鎖的,會出現(xiàn)兩個進程同時訪問一個資源的情況。

解決方案:可以在進程1所在的jvm環(huán)境中開一個線程專門用來“續(xù)命”,當(dāng)需要解鎖的時候,通知這個續(xù)命線程結(jié)束執(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 請求標(biāo)識* @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;}

歡迎工作一到五年的Java工程師朋友們加入Java架構(gòu)開發(fā): 855835163
群內(nèi)提供免費的Java架構(gòu)學(xué)習(xí)資料(里面有高可用、高并發(fā)、高性能及分布式、Jvm性能調(diào)優(yōu)、Spring源碼,MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多個知識點的架構(gòu)資料)合理利用自己每一分每一秒的時間來學(xué)習(xí)提升自己,不要再用"沒有時間“來掩飾自己思想上的懶惰!趁年輕,使勁拼,給未來的自己一個交代!

轉(zhuǎn)載于:https://my.oschina.net/u/3959468/blog/2878407

總結(jié)

以上是生活随笔為你收集整理的锁和分布式锁的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。