天天看點

java多個鎖如何維護_共享資源那麼多,如何用一把鎖保護多個資源?

寫在前面

上一篇文章原子性問題的宏觀了解 帶領大家了解了鎖和資源的模型,有了這篇文章的鋪墊,相信了解這一篇文章就非常輕松了

當我們要保護單個資源并對其進行修改其實很簡單,隻需按照下圖分三步走

建立受保護資源 R 的鎖

加鎖進入臨界區

解鎖走出臨界區

java多個鎖如何維護_共享資源那麼多,如何用一把鎖保護多個資源?

上圖的關鍵是「R1 的鎖保護 R1」的指向關系是否正确

如果都是保護單個資源這樣簡單,程式猿的世界該有多美好,可惜并不是,通常我們需要保護多個資源

保護多個資源

保護多個沒有關系的資源

如果多個資源沒有關系,那就是保護一個資源模型的複制,同樣非常簡單,且看下圖:

java多個鎖如何維護_共享資源那麼多,如何用一把鎖保護多個資源?

比如現實中銀行取款和修改密碼操作。

銀行取款操作對應的資源是「餘額」, 修改密碼操作對應的資源是「密碼」,餘額和密碼兩個資源完全沒有關系,是以各自用自家的鎖保護自家的資源就好了

如果多個資源沒有關系,程式猿的世界該有多美好,可惜并不是,我們保護的資源多數情況都有關聯關系

保護多個關系的資源

拿經典的銀行轉賬案例來說明,賬戶 A 給賬戶 B 轉賬,賬戶 A 餘額減少 100 元,賬戶 B 餘額增加 100 元,這個操作要是原子性的,那麼資源「A 餘額」和資源「B 餘額」就這樣"有了關系",先來看程式:

class Account {

private int balance;

// 轉賬

synchronized void transfer(

Account target, int amt){

if (this.balance > amt) {

this.balance -= amt;

target.balance += amt;

}

}

}

用 synchronized 直接保護 transfer 方法,然後操作資源「A 餘額」和資源「B 餘額」就可以了

⚠️: 真的是這樣嗎?

先停止向下看,在你的筆記本上按照文章開頭的三步走來畫個圖看一看,是否和下圖一樣呢?

java多個鎖如何維護_共享資源那麼多,如何用一把鎖保護多個資源?

我們通常容易忽略鎖和資源的指向關系,我們想當然的用鎖 this 來保護 target 資源了,也就沒有起到保護作用

假設 A,B,C 賬戶初始餘額都是 200 原,A 向 B 轉賬 100,B 向 C 轉賬 100

我們期盼最終的結果是:

賬戶 A 餘額: 100 元

賬戶 B 餘額: 200 元

賬戶 C 餘額: 300 元

假線程 1「A 向 B 轉賬」與線程 2「B 向 C 轉賬」兩個操作同時執行,根據 JMM 模型可知,線程 1 和線程 2 讀取線程 B 目前的餘額都是 200 元:

線程 1 執行 transfer 方法鎖定的是 A 的執行個體(A.this),并沒有鎖定 B 的執行個體

線程 2 執行 transfer 方法鎖定的是 B 的執行個體(B.this),并沒有鎖定 C 的執行個體

是以線程 1 和線程 2 可以同時進入 transfer 臨界區,上面你認為對的模型其實就會變成這個樣子:

java多個鎖如何維護_共享資源那麼多,如何用一把鎖保護多個資源?

還記得 happens-before 規則 這篇文章提到的螢幕鎖規則和傳遞性規則嗎?

螢幕鎖規則

對一個鎖的解鎖 happens-before 于随後對這個鎖的加鎖

傳遞性規則

如果 A happens-before B, 且 B happens-before C, 那麼 A happens-before C

資源 B.balance 存在于兩個"臨界區"中,是以這個"臨界區"對 B.balance 來說形同虛設,也就不滿足螢幕鎖規則,進而導緻傳遞性規則也不生效,說白了,前序線程的更改結果對後一個線程不可見

這樣最終導緻:

賬戶 B 的餘額可能是 100: 線程 1 寫 B.balance 100(balance = 300) 先于 線程 2 寫 B.balance(balance = 100),也就是說線程 1 的結果會被線程 2 覆寫,導緻最終賬戶 B 的餘額為 100

賬戶 B 的餘額可能是 300: 與上述情況相反,線程 1 寫 B.balance 100(balance = 300) 後于 線程 2 寫 B.balance(balance = 100),也就是說線程 2 的結果線程 1 覆寫,導緻最終賬戶 B 的餘額為 300

就是不能得到我們理想結果 200,感覺生活無比的艱難,那怎麼辦呢?

正确姿勢

上面的問題就是為資源建立的鎖不能保護所有關聯的資源,那我們就想辦法解決這個問題,來看下面代碼:

class Account {

private int balance;

// 轉賬

void transfer(Account target, int amt){

synchronized(Account.class) {

if (this.balance > amt) {

this.balance -= amt;

target.balance += amt;

}

}

}

}

我們将 this 鎖變為 Account.class 鎖,Account.class 是虛拟機加載 Account 類時建立的,肯定是唯一的(雙親委派模型解釋了為何該對象是唯一的), 所有 Account 對象都共享 Account.class, 也就是說,Account.class 鎖能保護所有 Account 對象,我們将上面程式再用模型解釋一下

java多個鎖如何維護_共享資源那麼多,如何用一把鎖保護多個資源?

總結

到這裡關于鎖和資源的關系你應該了解的更加透徹了,單個資源和多個無關聯資源的情形都很好處理,為各自資源建立相應的鎖就好,如果多個資源有關聯,為了讓鎖起到保護作用,我們需要将鎖的粒度變大,比如将 this 鎖變成了 Account.class 鎖。

轉賬業務非常常見,并發量非常大,如果我們将鎖的粒度都提升到 Account.class 這個級别(分久必合),假設每次轉賬業務都很耗時,那麼顯然這個鎖的性能是比較低的,是以接下來的文章,我們還會繼續優化這個模型,選擇合适的鎖粒度,同時能保護多個有關聯的資源,

我們的鎖粒度雖然大,但是我們保障了賬戶的安全,是以并發程式設計可以先保證事情做對,遇到瓶頸了,慢慢優化改變相應的模型就好了,當然熟練了解這個模型以後,一步到位的并發程式設計模型當然是極好的......

靈魂追問

還記得 happens-before 的幾個原則嗎?

偏向鎖,輕量鎖,重量鎖是不是和我們這節内容有異曲同工之處呢?

提前想一下,我們如何來優化這個模型呢?

附加說明

如果你對這篇文章了解有些困難,可以按照下面的順序回憶前序文章相關内容

推薦閱讀

提高效率工具

java多個鎖如何維護_共享資源那麼多,如何用一把鎖保護多個資源?

歡迎持續關注公衆号:「日拱一兵」

前沿 Java 技術幹貨分享

高效工具彙總 | 回複「工具」

面試問題分析與解答

技術資料領取 | 回複「資料」

以讀偵探小說思維輕松趣味學習 Java 技術棧相關知識,本着将複雜問題簡單化,抽象問題具體化和圖形化原則逐漸分解技術問題,技術持續更新,請持續關注......

java多個鎖如何維護_共享資源那麼多,如何用一把鎖保護多個資源?

關于找一找教程網

本站文章僅代表作者觀點,不代表本站立場,所有文章非營利性免費分享。

本站提供了軟體程式設計、網站開發技術、伺服器運維、人工智能等等IT技術文章,希望廣大程式員努力學習,讓我們用科技改變世界。

[共享資源那麼多,如何用一把鎖保護多個資源?]http://www.zyiz.net/tech/detail-92962.html