如何鎖以及分布式鎖

如何鎖以及分布式鎖,相信很多沒(méi)有經(jīng)驗(yàn)的人對(duì)此束手無(wú)策,為此本文總結(jié)了問(wèn)題出現(xiàn)的原因和解決方法,通過(guò)這篇文章希望你能解決這個(gè)問(wèn)題。

為平塘等地區(qū)用戶提供了全套網(wǎng)頁(yè)設(shè)計(jì)制作服務(wù),及平塘網(wǎng)站建設(shè)行業(yè)解決方案。主營(yíng)業(yè)務(wù)為成都網(wǎng)站設(shè)計(jì)、成都網(wǎng)站建設(shè)、平塘網(wǎng)站設(shè)計(jì),以傳統(tǒng)方式定制建設(shè)網(wǎng)站,并提供域名空間備案等一條龍服務(wù),秉承以專(zhuān)業(yè)、用心的態(tài)度為用戶提供真誠(chéng)的服務(wù)。我們深信只要達(dá)到每一位用戶的要求,就會(huì)得到認(rèn)可,從而選擇與我們長(zhǎng)期合作。這樣,我們也可以走得更遠(yuǎn)!

在多線程的軟件世界里,對(duì)共享資源的爭(zhēng)搶過(guò)程(Data Race)就是并發(fā),而對(duì)共享資源數(shù)據(jù)進(jìn)行訪問(wèn)保護(hù)的最直接辦法就是引入鎖!。

無(wú)鎖編程也是一種辦法,但它不在本文的討論范圍,并發(fā)多線程轉(zhuǎn)為單線程(Disruptor),函數(shù)式編程,鎖粒度控制(ConcurrentHashMap桶),信號(hào)量(Semaphore)等手段都可以實(shí)現(xiàn)無(wú)鎖或鎖優(yōu)化。

技術(shù)上來(lái)說(shuō),鎖也可以理解成將大量并發(fā)請(qǐng)求串行化,但請(qǐng)注意串行化不能簡(jiǎn)單等同為排隊(duì) ,因?yàn)檫@里和現(xiàn)實(shí)世界沒(méi)什么不同,排隊(duì)意味著大家是公平Fair的領(lǐng)到資源,先到先得,然而很多情況下為了性能考量多線程之間還是會(huì)不公平Unfair的去搶。Java中ReentrantLock可重入鎖,提供了公平鎖和非公平鎖兩種實(shí)現(xiàn)

再注意一點(diǎn),串行也不是意味著只有一個(gè)排隊(duì)的隊(duì)伍,每次只能進(jìn)一個(gè)。當(dāng)然可以好多個(gè)隊(duì)伍,每次進(jìn)入多個(gè)。比如餐館一共10個(gè)餐桌,服務(wù)員可能一次放行最多10個(gè)人進(jìn)去,有人出來(lái)再放行同數(shù)量的人進(jìn)去。Java中Semaphore信號(hào)量,相當(dāng)于同時(shí)管理一批鎖

鎖的類(lèi)型

1 自旋鎖 (Spin Lock)

自旋鎖如果已經(jīng)被別的線程獲取,調(diào)用者就一直循環(huán)在那里看是否該自旋鎖的保持者已經(jīng)釋放了鎖,”自旋”一詞就是因此而得名。

自旋鎖是一種非阻塞鎖,也就是說(shuō),如果某線程需要獲取自旋鎖,但該鎖已經(jīng)被其他線程占用時(shí),該線程不會(huì)被掛起,而是在不斷的消耗CPU的時(shí)間,不停的試圖獲取自旋鎖。

Java沒(méi)有默認(rèn)的自旋鎖實(shí)現(xiàn),示例代碼如下:

public class SpinLock {
 private AtomicReference<Thread> sign =new AtomicReference<>();
 public void lock(){
   Thread current = Thread.currentThread();
   while(!sign .compareAndSet(null, current)){
   }
 }
 public void unlock (){
   Thread current = Thread.currentThread();
   sign .compareAndSet(current, null);
 }
}

通過(guò)示例,可以看到CAS原子操作將sign從期望的null設(shè)置為當(dāng)前線程,線程A第一次調(diào)用lock()可以獲取鎖,第二次調(diào)用將進(jìn)入循環(huán)等待,因?yàn)閟ign已經(jīng)被設(shè)置為了current。
簡(jiǎn)單加個(gè)當(dāng)前鎖的owner比對(duì)判斷和鎖計(jì)數(shù)器,即可實(shí)現(xiàn)重入。

2 互斥鎖 (Mutex Lock)

互斥鎖是阻塞鎖,當(dāng)某線程無(wú)法獲取互斥鎖時(shí),該線程會(huì)被直接掛起,不再消耗CPU時(shí)間,當(dāng)其他線程釋放互斥鎖后,操作系統(tǒng)會(huì)喚醒那個(gè)被掛起的線程。

阻塞鎖可以說(shuō)是讓線程進(jìn)入阻塞狀態(tài)進(jìn)行等待,當(dāng)獲得相應(yīng)的信號(hào)(喚醒,時(shí)間)時(shí),才可以進(jìn)入線程的準(zhǔn)備就緒狀態(tài),準(zhǔn)備就緒狀態(tài)的所有線程,通過(guò)競(jìng)爭(zhēng)進(jìn)入運(yùn)行狀態(tài)。它的優(yōu)勢(shì)在于,阻塞的線程不會(huì)占用 CPU 時(shí)間, 不會(huì)導(dǎo)致 CPU 占用率過(guò)高,但進(jìn)入時(shí)間以及恢復(fù)時(shí)間都要比自旋鎖略慢。在競(jìng)爭(zhēng)激烈的情況下阻塞鎖的性能要明顯高于自旋鎖。

JAVA中,能夠進(jìn)入/退出、阻塞狀態(tài)或包含阻塞鎖的方法有:
synchronized
ReentrantLock
Object.wait()/notify()
LockSupport.park()/unpart()(j.u.c經(jīng)常使用)

自旋鎖 VS 互斥鎖
兩種鎖適用于不同場(chǎng)景:
如果是多核處理器,預(yù)計(jì)線程等待鎖的時(shí)間很短,短到比線程兩次上下文切換時(shí)間要少的情況下,使用自旋鎖是劃算的。

如果是多核處理器,如果預(yù)計(jì)線程等待鎖的時(shí)間較長(zhǎng),至少比兩次線程上下文切換的時(shí)間要長(zhǎng),建議使用互斥鎖。

如果是單核處理器,一般建議不要使用自旋鎖。因?yàn)椋谕粫r(shí)間只有一個(gè)線程是處在運(yùn)行狀態(tài),那如果運(yùn)行線程發(fā)現(xiàn)無(wú)法獲取鎖,只能等待解鎖,但因?yàn)樽陨聿粧炱?,所以那個(gè)獲取到鎖的線程沒(méi)有辦法進(jìn)入運(yùn)行狀態(tài),只能等到運(yùn)行線程把操作系統(tǒng)分給它的時(shí)間片用完,才能有機(jī)會(huì)被調(diào)度。這種情況下使用自旋鎖的代價(jià)很高。

如果加鎖的代碼經(jīng)常被調(diào)用,但競(jìng)爭(zhēng)情況很少發(fā)生時(shí),應(yīng)該優(yōu)先考慮使用自旋鎖,自旋鎖的開(kāi)銷(xiāo)比較小,互斥量的開(kāi)銷(xiāo)較大。

3 可重入鎖 (Reentrant Lock)

可重入鎖是一種特殊的互斥鎖,它可以被同一個(gè)線程多次獲取,而不會(huì)產(chǎn)生死鎖。

  1. 首先它是互斥鎖:任意時(shí)刻,只有一個(gè)線程鎖。即假設(shè)A線程已經(jīng)獲取了鎖,在A線程釋放這個(gè)鎖之前,B線程是無(wú)法獲取到這個(gè)鎖的,B要獲取這個(gè)鎖就會(huì)進(jìn)入阻塞狀態(tài)。

  2. 其次,它可以被同一個(gè)線程多次持有。即,假設(shè)A線程已經(jīng)獲取了這個(gè)鎖,如果A線程在釋放鎖之前又一次請(qǐng)求獲取這個(gè)鎖,那么是能夠獲取成功的。

Java中的synchronized, ReentrantLock都是可重入鎖。

4 輕量級(jí)鎖(Lightweight Lock) & 偏向鎖(Biased Lock)

首先互斥是一種會(huì)導(dǎo)致線程掛起,并在較短時(shí)間內(nèi)又需要重新調(diào)度回原線程的,較為消耗資源的操作。

Java6為了減少獲得鎖和釋放鎖所帶來(lái)的性能消耗,引入了“偏向鎖”和“輕量級(jí)鎖”,所以在Java6里鎖一共有四種狀態(tài),無(wú)鎖狀態(tài),偏向鎖狀態(tài),輕量級(jí)鎖狀態(tài)和重量級(jí)鎖狀態(tài),它會(huì)隨著競(jìng)爭(zhēng)情況逐漸升級(jí)。鎖可以升級(jí)但不能降級(jí),意味著偏向鎖升級(jí)成輕量級(jí)鎖后不能降級(jí)成偏向鎖。這種鎖升級(jí)卻不能降級(jí)的策略,目的是為了提高獲得鎖和釋放鎖的效率。

數(shù)據(jù)庫(kù)中針對(duì)不同的鎖層級(jí)(Lock Hierarchy,表/頁(yè)/行等),
也有類(lèi)似鎖升級(jí)(Lock Escalations)的理念。

5 JUC

并發(fā)大師Doug Lea在JUC包中實(shí)現(xiàn)了大量的并發(fā)工具類(lèi),并發(fā)思想在源碼中得到了很好的體現(xiàn)。比如Semaphore, CountDownLatch, CyclicBarrier都是特定場(chǎng)景下的經(jīng)典實(shí)現(xiàn),大家有興趣可以自行研究,最終一嘆: 鎖 原來(lái)可以玩出這么多花樣來(lái)。

如何鎖以及分布式鎖

java-7-concurrent-executors-uml-class-diagram-example

鎖的后遺癥

在并發(fā)世界里,鎖扮演了一個(gè)個(gè)亦正亦邪的角色,甚至很多時(shí)候是個(gè)大反派。鎖的后遺癥包括:死鎖,饑餓,活鎖,Lock Convoying(多個(gè)同優(yōu)先級(jí)的線程重復(fù)競(jìng)爭(zhēng)同一把鎖,此時(shí)大量雖然被喚醒而得不到鎖的線程被迫進(jìn)行調(diào)度切換,這種頻繁的調(diào)度切換相當(dāng)影響系統(tǒng)性能),優(yōu)先級(jí)反轉(zhuǎn),不公平和低效率等。而這些問(wèn)題都是在實(shí)現(xiàn)鎖的過(guò)程中普遍存在而又不得不面對(duì)的。
這里只拋出問(wèn)題讓讀者了解,具體解決方案不在本文范疇。

活鎖和死鎖的區(qū)別在于,處于活鎖的實(shí)體是在不斷的改變狀態(tài),所謂之“活”, 而處于死鎖的實(shí)體表現(xiàn)為等待;活鎖有可能自行解開(kāi),死鎖則不能。

分布式鎖

相對(duì)于單機(jī)應(yīng)用設(shè)定的單機(jī)鎖,為分布式應(yīng)用各節(jié)點(diǎn)對(duì)共享資源的排他式訪問(wèn)而設(shè)定的鎖就是分布式鎖。在分布式場(chǎng)景下,有很多種情況都需要實(shí)現(xiàn)多節(jié)點(diǎn)的最終一致性。比如全局發(fā)號(hào)器,分布式事務(wù)等等。

傳統(tǒng)實(shí)現(xiàn)分布式鎖的方案一般是利用持久化數(shù)據(jù)庫(kù)(如利用InnoDB行鎖,或事務(wù),或version樂(lè)觀鎖),當(dāng)然大部分時(shí)候可以滿足大部分人的需求。而如今互聯(lián)網(wǎng)應(yīng)用的量級(jí)已經(jīng)幾何級(jí)別的爆發(fā),利用諸如zookeeper,redis等更高效的分布式組件來(lái)實(shí)現(xiàn)分布式鎖,可以提供高可用的更強(qiáng)壯的鎖特性,并且支持豐富化的使用場(chǎng)景。
開(kāi)源實(shí)現(xiàn)已有不少比如Redis作者基于Redis設(shè)計(jì)的Redlock,Redission等

小插曲:
鎖存在的地方就有爭(zhēng)議,Redlock也不例外。有一位分布式專(zhuān)家曾經(jīng)發(fā)表過(guò)一片文章<How to do distributed locking>, 質(zhì)疑Redlock的正確性,Redis作者則在<Is Redlock safe?>中給予了回應(yīng),爭(zhēng)鋒相對(duì)精彩無(wú)比,有興趣的讀者可以自行前往。

前人栽樹(shù)后人乘涼,當(dāng)下各種的鎖實(shí)現(xiàn)已經(jīng)給我們提供了很多優(yōu)雅的設(shè)計(jì)范本,我們具體來(lái)分析下分布式鎖到底應(yīng)該怎么設(shè)計(jì)呢?

分布式鎖的設(shè)計(jì)要點(diǎn)

我們以Redis為例,簡(jiǎn)單思考下這個(gè)鎖的實(shí)現(xiàn)。
似乎加鎖的時(shí)候只要一個(gè) SETNX 命令就搞定了,返回1代表加鎖成功,返回0 表示鎖被占用著。然后再用 DEL 命令解鎖,返回1表示解鎖成功,0表示已經(jīng)被解鎖過(guò)。
接著問(wèn)題就來(lái)了:
SETNX會(huì)存在鎖競(jìng)爭(zhēng),如果在執(zhí)行過(guò)程中客戶端宕機(jī),會(huì)引起死鎖問(wèn)題,也就是鎖資源無(wú)法釋放。解決死鎖的問(wèn)題其實(shí)可以可以向MySQL的死鎖檢測(cè)學(xué)習(xí),設(shè)置一個(gè)失效時(shí)間,通過(guò)key的時(shí)間戳來(lái)判斷是否需要強(qiáng)制解鎖。
但是強(qiáng)制解鎖也存在問(wèn)題,一個(gè)就是時(shí)間差問(wèn)題,不同的機(jī)器的本地時(shí)間可能也存在時(shí)間差,在很小事務(wù)粒度的高并發(fā)場(chǎng)景下還是會(huì)存在問(wèn)題,比如刪除鎖的時(shí)候,會(huì)判斷時(shí)間戳已經(jīng)超過(guò)時(shí)效,有可能刪除其他已經(jīng)獲取鎖的客戶端的鎖。
另外,如果設(shè)置了一個(gè)超時(shí)時(shí)間,若程序執(zhí)行時(shí)間超過(guò)了超時(shí)時(shí)間,那么還沒(méi)執(zhí)行完鎖會(huì)被自動(dòng)釋放,原來(lái)持鎖的客戶端再次解鎖的時(shí)候會(huì)出現(xiàn)問(wèn)題,而且最為嚴(yán)重的還是一致性沒(méi)有得到保障。如何合理的設(shè)置這個(gè)超時(shí)時(shí)間可能是一個(gè)觀測(cè)并不斷調(diào)整的過(guò)程。

那么,總結(jié)下設(shè)計(jì)的幾個(gè)要點(diǎn):

  • 鎖的時(shí)效。避免單點(diǎn)故障造成死鎖,影響其他客戶端獲取鎖。但是也要保證一旦一個(gè)客戶端持鎖,在客戶端可用時(shí)不會(huì)被其他客戶端解鎖。

  • 持鎖期間的check。盡量在關(guān)鍵節(jié)點(diǎn)檢查鎖的狀態(tài),所以要設(shè)計(jì)成可重入鎖。

  • 減少獲取鎖的操作,盡量減少redis壓力。所以需要讓客戶端的申請(qǐng)鎖有一個(gè)等待時(shí)間,而不是所有申請(qǐng)鎖的請(qǐng)求線程不斷的循環(huán)申請(qǐng)鎖。

  • 加鎖的事務(wù)或者操作盡量粒度小,減少其他客戶端申請(qǐng)鎖的等待時(shí)間,提高處理效率和并發(fā)性。

  • 持鎖的客戶端解鎖后,要能通知到其他等待鎖的節(jié)點(diǎn),否則其他節(jié)點(diǎn)只能一直等待一個(gè)預(yù)計(jì)的時(shí)間再觸發(fā)申請(qǐng)鎖。類(lèi)似線程的notifyAll,要能同步鎖狀態(tài)給其他客戶端,并且是分布式消息。

  • 考慮任何執(zhí)行句柄中可能出現(xiàn)的異常,狀態(tài)的正確流轉(zhuǎn)和處理。比如,不能因?yàn)橐粋€(gè)節(jié)點(diǎn)解鎖失敗,或者鎖查詢失?。╮edis 超時(shí)或者其他運(yùn)行時(shí)異常),影響整個(gè)等待的任務(wù)隊(duì)列,或者任務(wù)池。

  • 若Redis服務(wù)器宕機(jī)或者網(wǎng)絡(luò)異常,要有其他備份方案,比如單機(jī)鎖限流+最終數(shù)據(jù)庫(kù)的持久化鎖來(lái)做好最終一致性控制。

看完上述內(nèi)容,你們掌握如何鎖以及分布式鎖的方法了嗎?如果還想學(xué)到更多技能或想了解更多相關(guān)內(nèi)容,歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道,感謝各位的閱讀!

分享名稱(chēng):如何鎖以及分布式鎖
當(dāng)前URL:http://muchs.cn/article34/iiogpe.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供手機(jī)網(wǎng)站建設(shè)、品牌網(wǎng)站制作、企業(yè)建站、響應(yīng)式網(wǎng)站、網(wǎng)站設(shè)計(jì)公司、

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)

網(wǎng)站建設(shè)網(wǎng)站維護(hù)公司