1. 為什么要使用分布式鎖?

- 變量A存在JVM1、JVM2、JVM3三個JVM(進程)內(nèi)存中
- 變量A同時都會在JVM分配一塊內(nèi)存,三個請求發(fā)過來同時對這個變量進行操作(例如3個人買書,書的庫存只有2本),顯然結果是不對的
- 不是同時發(fā)過來,三個請求分別操作三個不同JVM內(nèi)存區(qū)域的數(shù)據(jù),變量A之間不存在共享,也不具有可見性,處理的結果也是不對的
注:這個變量主要體現(xiàn)在一個類中有一個成員變量,它是一個有狀態(tài)的對象
2. 分布式鎖應該具備哪些條件?
- 在分布式系統(tǒng)環(huán)境下,一個方法在同一時間只能被一個機器的一個線程執(zhí)行
- 高可用的獲取與釋放鎖(zookeeper一直都能提供鎖,并且釋放鎖)
- 高性能的獲取鎖與釋放鎖(快)
- 具備可重入性特性(可理解為重新進入,由多于一個任務并發(fā)使用,而不必擔心數(shù)據(jù)錯誤)
可重入(reentrant)函數(shù)可以由多于一個任務并發(fā)使用,而不必擔心數(shù)據(jù)錯誤。相反,不可重入(non-reentrant)函數(shù)不能由超過一個任務所共享,除非能確保函數(shù)的互斥(或者使用信號量,或者在代碼的關鍵部分禁用中斷)??芍厝牒瘮?shù)可以在任意時刻被中斷,稍后再繼續(xù)運行,不會丟失數(shù)據(jù)??芍厝牒瘮?shù)要么使用本地變量,要么在使用全局變量時保護自己的數(shù)據(jù)。
可重入函數(shù):
1.不為連續(xù)的調用持有靜態(tài)數(shù)據(jù)。
2.不返回指向靜態(tài)數(shù)據(jù)的指針;所有數(shù)據(jù)都由函數(shù)的調用者提供。
3.使用本地數(shù)據(jù),或者通過制作全局數(shù)據(jù)的本地拷貝來保護全局數(shù)據(jù)。
4.如果必須訪問全局變量,記住利用互斥信號量來保護全局變量。
5.絕不調用任何不可重入函數(shù)。
- 具備鎖失效機制,防止死鎖
回顧:多線程是如何避免死鎖的?
我們只要破壞產(chǎn)生死鎖的四個條件中的其中一個就可以了。
1.破壞互斥條件
這個條件我們沒有辦法破壞,因為我們用鎖本來就是想讓他們互斥的(臨界資源需要互斥訪問)。
2.破壞請求與保持條件
一次性申請所有的資源。
3.破壞不剝奪條件
占用部分資源的線程進一步申請其他資源時,如果申請不到,可以主動釋放它占有的資源。
4.破壞循環(huán)等待條件
靠按序申請資源來預防。按某一順序申請資源,釋放資源則反序釋放。破壞循環(huán)等待條件。
我們對線程 2 的代碼修改成下面這樣就不會產(chǎn)生死鎖了
- 具備非阻塞鎖特性,即沒有獲取到鎖將直接返回獲取鎖失敗
說明:類似于熔斷機制,避免大量申請鎖等待而導致的阻塞.
什么是Zookeeper
Zookeeper是一個為分布式應用提供一致性服務的開源組件,它內(nèi)部是一個分層的文件系統(tǒng)目錄樹結構,規(guī)定同一個目錄下只能有一個唯一的文件名,即同一目錄下文件名不能重復
Zookeeper實現(xiàn)分布式鎖的步驟
- 創(chuàng)建一個目錄MyZookeeper
- 線程A想要獲取鎖就在MyZookeeper目錄下創(chuàng)建一個臨時順序節(jié)點
- 線程A獲取MyZookeeper目錄下所有的子節(jié)點,然后獲取比自己小的兄弟節(jié)點,如果不存在,則說明當前線程順序號最小,獲得鎖
- 此時線程B也獲取鎖,它會先獲取所有的MyZookeeper節(jié)點下的子節(jié)點,如果判斷自己不是最小的節(jié)點,就設置監(jiān)聽比自己次小的節(jié)點
- 線程A處理完畢,刪除自己的節(jié)點,線程B監(jiān)聽到變更事件(加入線程B次小的節(jié)點是A),還是判斷下自己是不是最小的節(jié)點,如果是就獲得鎖
參考:單點故障與分布式鎖