互斥鎖(下):如何用一把鎖保護多個資源?

Love&Share發表於2022-03-04

1)當我們要保護多個資源時,首先要做的是什麼?

  • 分析這些資源是否存在關聯關係

2)應該怎樣保護沒有關聯關係的多個資源?

  • 應該怎樣保護沒有關聯關係的多個資源?**就是沒有關聯關係的,這種場景非常容易解決,那就是球賽有球賽的門票,電影院有電影院的門票,各自管理各自的

  • 對應到程式設計領域,例如,銀行業務中有針對賬戶餘額(餘額是一種資源)的取款操作,也有針對賬戶密碼(密碼也是一種資源)的更改操作,我們可以為賬戶餘額和賬戶密碼分配不同的鎖來解決併發問題

3)保護沒有關聯關係的多個資源例項?

  • 管密碼的是一把鎖,管餘額的是另外一把鎖,各管各的,井水不犯河水。

 
 class Account {
   // 鎖:保護賬戶餘額
   private final Object balLock
     = new Object();
   // 賬戶餘額  
   private Integer balance;
   // 鎖:保護賬戶密碼
   private final Object pwLock
     = new Object();
   // 賬戶密碼
   private String password;
 
   // 取款
   void withdraw(Integer amt) {
     synchronized(balLock) {
       if (this.balance > amt){
         this.balance -= amt;
      }
    }
  }
   // 檢視餘額
   Integer getBalance() {
     synchronized(balLock) {
       return balance;
    }
  }
 
   // 更改密碼
   void updatePassword(String pw){
     synchronized(pwLock) {
       this.password = pw;
    }
  }
   // 檢視密碼
   String getPassword() {
     synchronized(pwLock) {
       return password;
    }
  }
 }

3.1)上面的程式碼中我們可以用一把鎖來保護密碼和餘額這兩個資源嗎?

  • 可以,在所有方法上加synchronized關鍵字,鎖都是this。

3.2)用一把鎖管密碼和餘額會存在什麼問題?

  • 效能太差,會導致取款、檢視餘額、修改密碼、檢視密碼這四個操作都是序列的

3.3)那使用兩把鎖的優點是什麼?

  • 用兩把鎖,取款和修改密碼是可以並行的。

  • 不同的鎖對受保護資源進行精細化管理,能夠提升效能

3.4)這種進行精細化管理的鎖叫什麼名字?

  • 細粒度鎖

4)應該怎樣保護有關聯關係的多個資源?

  • 銀行業務裡面的轉賬操作,賬戶 A 減少 100 元,賬戶 B 增加 100 元。這兩個賬戶就是有關聯關係的

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

4.1)怎麼保證轉賬操作 transfer() 沒有併發問題呢?用 synchronized 關鍵字修飾一下 transfer() 方法就可以了嗎?

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

兩個資源,一把this鎖,這看上去完全正確的啊,有什麼問題?

4.2)上面程式碼中的問題出現在哪裡?

  • 問題就出在 this 這把鎖上。this 這把鎖可以保護自己的餘額 this.balance,卻保護不了別人的餘額 target.balance。

  • 你不能用自家的鎖來保護別人家的資產,也不能用自己的票來保護別人的座位一樣。

4.3)上面tihs鎖模型圖是怎樣的?

 

 

4.4)this只能保護自己,不能保護他人的例項?

A、B、C 三個賬戶,餘額都是 200 元。

兩個執行緒分別執行兩個轉賬操作

  • A 轉給賬戶 B 100 元

  • B 轉給賬戶 C 100 元

期望的結果A 是 100 元, B 是 200 元, C 是 300 元。

假設執行緒 1 執行賬戶 A 轉賬戶 B 的操作,執行緒 2 執行賬戶 B 轉賬戶 C 的操作。

4.4.1)這兩個執行緒分別在兩顆 CPU 上同時執行,那它們是互斥的嗎?

  • 不是。執行緒 1 鎖定的是賬戶 A 的例項(A.this),執行緒 2 鎖定的是賬戶 B 的例項(B.this)。鎖的物件不一樣,他們兩可以同時進入臨界區,然後把B=200的值都讀到自己的執行緒資源裡面。

4.4.2)當兩個執行緒執行完成後,B可能的值有哪些?

  • 100 :執行緒1先執行,執行緒2後執行然後覆蓋掉執行緒1的B值。

  • 300:執行緒2先執行,執行緒1後執行然後覆蓋掉執行緒1的B值。

  • 反正就是不可能是正確的200!

 

 

4.5)那麼該怎樣正確的去使用一把鎖避免兩個關聯資源沒有保護周全的情況?

  • 用一把範圍大的鎖

在上面的例子中,this 是物件級別的鎖,所以 A 物件和 B 物件都有自己的鎖。

4.6)如何讓 A 物件和 B 物件共享一把鎖呢?

  • 讓所有物件都持有一個唯一性的物件,這個物件在建立 Account 時傳入。

     
     class Account {
       private Object lock;
       private int balance;
       private Account();
       // 建立Account時傳入同一個lock物件
       public Account(Object lock) {
         this.lock = lock;
      }
       // 轉賬
       void transfer(Account target, int amt){
         // 此處檢查所有物件共享的鎖
         synchronized(lock) {
           if (this.balance > amt) {
             this.balance -= amt;
             target.balance += amt;
          }
        }
      }
     }

    它要求在建立 Account 物件的時候必須傳入同一個物件,如果不是同一個那就完蛋了。

  • 用 Account.class 作為共享的鎖(選擇這種方案)

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

     

     

5)在第一個示例程式裡,我們用了兩把不同的鎖來分別保護賬戶餘額、賬戶密碼,建立鎖的時候,我們用的是:private final Object xxxLock = new Object();,如果賬戶餘額用 this.balance 作為互斥鎖,賬戶密碼用 this.password 作為互斥鎖,你覺得是否可以呢?

  • 不能用balance和password做為鎖物件。這兩個物件balance是Integer,password是String都是可變物件,一但對他們進行賦值就會變成新的物件,加的鎖就失效了。

相關文章