1. 程式人生 > >共享資源那麼多,如何用一把鎖保護多個資源?

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

寫在前面

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

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

  1. 建立受保護資源 R 的鎖
  2. 加鎖進入臨界區
  3. 解鎖走出臨界區

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

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

保護多個資源

保護多個沒有關係的資源

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

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

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

保護多個關係的資源

拿經典的銀行轉賬案例來說明,賬戶 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 餘額」就可以了

⚠️: 真的是這樣嗎?

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

我們通常容易忽略鎖和資源的指向關係,我們想當然的用鎖 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 臨界區,上面你認為對的模型其實就會變成這個樣子:

還記得 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 物件,我們將上面程式再用模型解釋一下

總結

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

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

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

靈魂追問

  1. 還記得 happens-before 的幾個原則嗎?
  2. 偏向鎖,輕量鎖,重量鎖是不是和我們這節內容有異曲同工之處呢?
  3. 提前想一下,我們如何來優化這個模型呢?

附加說明

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

  1. 這次走進併發的世界,請不要錯過
  2. 學併發程式設計,透徹理解這三個核心是關鍵
  3. 併發Bug之源有三,請睜大眼睛看清它們
  4. 可見性有序性,Happens-before來搞定
  5. 解決原子性問題?你首先需要的是巨集觀理解
  6. 面試併發volatile關鍵字時,我們應該具備哪些談資?

推薦閱讀

  • 每天用SpringBoot,還不懂RESTful API返回統一資料格式是怎麼實現的?
  • 雙親委派模型:大廠高頻面試題,輕鬆搞定
  • EasyExcel 讀取 excel 真的很easy
  • 紅黑樹,超強動靜圖詳解,簡單易懂

提高效率工具


歡迎持續關注公眾號:「日拱一兵」

  • 前沿 Java 技術乾貨分享
  • 高效工具彙總 | 回覆「工具」
  • 面試問題分析與解答
  • 技術資料領取 | 回覆「資料」

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

相關推薦

共享資源那麼如何用一把保護資源

寫在前面 上一篇文章原子性問題的巨集觀理解 帶領大家瞭解了鎖和資源的模型,有了這篇文章的鋪墊,相信理解這一篇文章就非常輕鬆了 當我們要保護單個資源並對其進行修改其實很簡單,只需按照下圖分三步走 建立受保護資源 R 的鎖 加鎖進入臨界區 解鎖走出臨界區 上圖的關鍵是「R1 的鎖保護 R1」的指向關係是否

linux——如何搭建samba共享目錄中的戶認證共享及匿名共享?(不分系統)

stat config alt 使用 files 設置 set 本地 ive 1.應用場景,搭建sam共享目錄可用於linux以及windows之間 2.服務端ip:192.168.56.11 (A)客戶端ip:192.168.56.133 (B) 查看防火墻狀

直播間人氣協議國內站群ip服務器怎麽實現呢?

軟件 服務器 穩定性 直播 代理 tid 增加 定性 數量 代理ip服務器(整C段多ip服務器)怎麽實現增加直播間的協議呢? 現在的很多直播平臺的或者公會為了吸引人氣 ,為主播 增加個人直播間的人氣,充排名,掛直播人氣, 那是他們是怎麽來實現直播間的人氣的增加呢 1、專門

PyCM 1.8 版本釋出 Python 編寫的類混淆矩陣庫

   PyCM 是一個用 Python 編寫的多類混淆矩陣庫,支援輸入資料向量和矩陣,是支援大多數類和統計引數的模型評估工具。主要針對資料科學家,用於預測模型指標、評估各種分類器的準確性。 PyCM 1.8 更新內容如下: Added Lift Score (LS) co

程式設計師加班那麼公司為什麼就不招幾程式設計師呢?

絕大部分的程式設計師都會存在經常加班的情況,至於為什麼程式設計師加班這麼多?大概分析出來有這麼幾個原因 1.和公司性質緊密相關,創業公司,中小型企業,網際網路公司,這三類公司是程式設計師加班的重災區,越是大型的網際網路公司加班現象越是嚴重,經常在半夜12點之後,有些大樓裡面

AI那麼你離失業還有遠?

在AI從業者的圈子裡,一直流傳著一個預言:未來,將是一個“技術性失業”的世界。 聽說,每一類AI新產品的釋出,都意味著某一個行業的人們將失去他們的飯碗。 聽說,AI將會導致數百萬的崗位消失。 然而,目前為止,聽說還只是聽說,大大小小的工廠裡還是遍佈往來穿梭的工人,城市還是需要那些早

壓測xx業務資料庫資源大量等待存在表問題導致資料庫無法正常執行解決辦法

壓測xxxx業務期間,監控oracle資料庫資源大量等待,存在表鎖問題及相關sql,如圖: 資料庫查詢詳細表鎖情況如圖: 跑xxxx業務操作的時候,資料庫無響應,經查詢,執行xx表無響應,經檢視,存在鎖表情況導致。原因是update xx表時候沒做commit操作。

ConcurrentHashMap原理(2)之分離實現執行緒間的併發寫操作

ConcurrentHashMap 類 ConcurrentHashMap 在預設併發級別會建立包含 16 個 Segment 物件的陣列。每個 Segment 的成員物件 table 包含若干個散列表的桶。每個桶是由 HashEntry 連結起來的一個連結串列。如果鍵能均

js拼接字串逗號隔開 ;兩陣列取不同

1. js拼接字串,用逗號隔開 //用jquery function getTextByJquery() { var str = ""; //遍歷name為txt的所有input元素 $("input[name='txt']").each(function () {

Java資源免費分享網盤自己拿 【Java資源免費分享網盤自己拿】

原 【Java資源免費分享,網盤自己拿】 2018年02月28日 19:01:34 Java3y 閱讀數:6843

程式設計師羨慕地鐵口賣餅夫妻:一個月能賺五萬比寫程式碼強

三百六十行,行行出狀元。可惜每個行業的人都羨慕另外行業的人,就像圍城效應一樣,一名程式設計師就在網路上感慨起來:高新園某地鐵口附近,賣餅的夫妻跟我說,一個月能賺五萬多的樣子,就是凌晨三點要起床和麵,唉,好羨慕,比寫程式碼強多了。 有程式設計師據此算了一筆賬:平均賺2.5萬一個,無社保獎金,

weUI應用JS封裝了幾常用的資訊提示的彈層

weUI應用,自己用JS封裝了幾個常用的資訊提示的彈層 測試頁面的程式碼在後面有貼出 幾個彈層如下圖 HTML頁面程式碼: <!DOCTYPE html> <html> <head> <title>weUI-test<

c語言 利用指標變數函式實現將3整數按從大到小的順序輸出

                利用指標變數,用函式實現將3個整數按從大到小的順序輸出。 解:程式: #include&l

ORA-00054 資源正忙但指定以NOWAIT 方式獲取資源或者超時失效

       1.查詢到正在相關資源。     select a.object_name objectname,              b.session_id,              c.serial#,              c.program    

9.死的概念、導致死的原因導致死的四必要條件預防死的方法、避免死的方法

死鎖避免策略      銀行家演算法:首先需要定義狀態和安全狀態的概念。系統的狀態是當前給程序分配的資源情況。因此,狀態包含兩個向量Resource(系統中每種資源的總量)和Available(未分配給程序的每種資源的總量)及兩個矩陣Claim(表示程序對資源的需求)和Allocation(表示當前分配給程

選用 Vala 的十理由 Vala 開發了170模組後的感悟

選用 Vala 作為工作語言開發新工具,開發了170個模組,應該對Vala稍微有一點發言權了,所以有了就有了這個系列分享。 &n

開發工具起來輕鬆解Word更隱藏技能!

你知道Word文件中有一個不常提到卻很實用的排版工具嗎?它就是開發工具。今天小編就給大家分享3個關於使用開發工具的小技巧。 1.怎麼製作選擇題的方框? 先點選"檔案",選擇"選項"--"自定義功能區",勾選開發工具點選確定即可開啟"開發工具"。 輸入文字,點選"開發工具",選擇"複選框內容控制元件",依

程序在螢幕列印資料時程序

from multiprocessing import Process, Lockdef f(l, i): #l.acquire() print('hello world', i) #l.release()if __name__ == '__main__': lock = Lock()

共享一款基於 jQuery 的功能對話方塊外掛 jBox強不強大了才知道:)

09年底開始創業的時候,在網上找不到一款適合自己用的對話方塊外掛,所以自己用了些時間寫了個jBox初版本,到今年8月才再花了不少私人時間來升級到2.0,完善了不少功能,請看下面的介紹: 外掛截圖 使用授權 - jBox 永久免費使用,但是必須保留相關的版權資訊。如果有好的建議,請Email: