1. 程式人生 > >如何避免死鎖?我們有套路可循

如何避免死鎖?我們有套路可循

寫在前面

上一篇文章共享資源那麼多,如何用一把鎖保護多個資源? 文章我們談到了銀行轉賬經典案例,其中有兩個問題:

  1. 單純的用 synchronized 方法起不到保護作用(不能保護 target)
  2. 用 Account.class 鎖方案,鎖的粒度又過大,導致涉及到賬戶的所有操作(取款,轉賬,修改密碼等)都會變成序列操作

如何解決這兩個問題呢?咱們先換好衣服穿越回到過去尋找一下錢莊,一起透過現象看本質,dengdeng deng.......

來到錢莊,告訴櫃員你要給鐵蛋兒轉 100 銅錢,這時櫃員轉身在牆上尋找你和鐵蛋兒的賬本,此時櫃員可能面臨三種情況:

  1. 理想狀態: 你和鐵蛋兒的賬本都是空閒狀態,一起拿回來,在你的賬本上減 100 銅錢,在鐵蛋兒賬本上加 100 銅錢,櫃員轉身將賬本掛回到牆上,完成你的業務
  2. 尷尬狀態: 你的賬本在,鐵蛋兒的賬本被其他櫃員拿出去給別人轉賬,你要等待其他櫃員把鐵蛋兒的賬本歸還
  3. 抓狂狀態: 你的賬本不在,鐵蛋兒的賬本也不在,你只能等待兩個賬本都歸還

放慢櫃員的取賬本操作,他一定是先拿到你的賬本,然後再去拿鐵蛋兒的賬本,兩個賬本都拿到(理想狀態)之後才能完成轉賬,用程式模型來描述一下這個拿取賬本的過程:

我們繼續用程式程式碼描述一下上面這個模型:

class Account {
  private int balance;
  // 轉賬
  void transfer(Account target, int amt){
    // 鎖定轉出賬戶
    synchronized(this) {              
      // 鎖定轉入賬戶
      synchronized(target) {           
        if (this.balance > amt) {
          this.balance -= amt;
          target.balance += amt;
        }
      }
    }
  } 
}

這個解決方案看起來很完美,解決了文章開頭說的兩個問題,但真是這樣嗎?


我們剛剛說過的理想狀態是錢莊只有一個櫃員(既單執行緒)。隨著錢莊規模變大,牆上早已掛了非常多個賬本,錢莊為了應對繁忙的業務,開通了多個視窗,此時有多個櫃員(多執行緒)處理錢莊業務。

櫃員 1 正在辦理給鐵蛋兒轉賬的業務,但只拿到了你的賬本;櫃員 2 正在辦理鐵蛋兒給你轉賬的業務,但只拿到了鐵蛋兒的賬本,此時雙方出現了尷尬狀態,兩位櫃員都在等待對方歸還賬本為當前客戶辦理轉賬業務。

現實中櫃員會溝通,喊出一嗓子 老鐵,鐵蛋兒的賬本先給我用一下,用完還給你,但程式卻沒這麼智慧,synchronized 內建鎖非常執著,它會告訴你「死等」的道理,最終出現死鎖

Java 有了 synchronized 內建鎖,還發明瞭顯示鎖 Lock,是不是就為了治一治 synchronized 「死等」的執著呢?