通過模擬銀行賬戶取錢操作,徹底瞭解Java多線程中的樂觀鎖和悲觀鎖!

悲觀鎖

總是假設最壞的情況,每次去拿數據的時候都認爲別人會修改,所以每次在拿數據的時候都會上鎖,這樣別人想拿這個數據就會阻塞直到它拿到鎖(共享資源每次只給一個線程使用,其它線程阻塞,用完後再把資源轉讓給其它線程)。傳統的關係型數據庫裏邊就用到了很多這種鎖機制,比如行鎖,表鎖等,讀鎖,寫鎖等,都是在做操作之前先上鎖。Java中synchronized和ReentrantLock等獨佔鎖就是悲觀鎖思想的實現。

樂觀鎖

總是假設最好的情況,每次去拿數據的時候都認爲別人不會修改,所以不會上鎖,但是在更新的時候會判斷一下在此期間別人有沒有去更新這個數據,可以使用版本號機制和CAS算法實現。樂觀鎖適用於多讀的應用類型,這樣可以提高吞吐量,像數據庫提供的類似於write_condition機制,其實都是提供的樂觀鎖。在Java中java.util.concurrent.atomic包下面的原子變量類就是使用了樂觀鎖的一種實現方式CAS實現的。

下面我就通過模擬銀行賬戶取錢的場景,在不加鎖、使用悲觀鎖和樂觀鎖的不同方式下看看程序的執行結果。希望此篇文章可以幫到正在努力的你。

  1. 先看一下無鎖狀態時程序執行的結果
package com.smallfan.atomic;

import java.util.ArrayList;
import java.util.List;

/**
* @PACKAGE_NAME: com.smallfan.atomic
* @NAME: AtomicTest
* @USER: dell
* @DATE: 2020/5/23
* @PROJECT_NAME: aboutthread
* 通過一個模擬銀行取錢的demo,徹底分清樂觀鎖和悲觀鎖
*/
public class AtomicTest {
   //
   public static void main(String[] args) {
       Account.testWithdraw(new AccountSafe(10000));
   }

}

class AccountSafe implements Account {
   private Integer balance;

   public AccountSafe(Integer balance) {
       this.balance = balance;
   }

   @Override
   public Integer getBalance() {
           return balance;
   }

   @Override
   public void withdraw(Integer amount) {
           balance -= amount;
   }
}

/**
* 定義賬戶的接口
*/
interface Account {
   // 獲取餘額
   Integer getBalance();

   // 取款
   void withdraw(Integer amount);

   /**
    * 方法內會啓動 1000 個線程,每個線程做 -10 元 的操作
    * 如果初始餘額爲 10000 那麼正確的結果應當是 0
    */
   static void testWithdraw(Account account) {
       List<Thread> ts = new ArrayList<>();
       long start = System.nanoTime();
       for (int i = 0; i < 1000; i++) {
           ts.add(new Thread(() -> {
               account.withdraw(10);
           }));
       }
       ts.forEach(Thread::start);
       ts.forEach(t -> {
           try {
               t.join();//等待線程執行結束
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
       });
       long end = System.nanoTime();
       System.out.println("最終結果還剩:"+account.getBalance() + " cost: " + (end - start) / 1000_000 + " ms");
   }
}

執行結果:最終結果還剩:830 cost: 270 ms

  1. 修改代碼添加synchronized關鍵字,實現悲觀鎖
/**
代碼修改部分 添加synchronized 關鍵字保證原子性操作
*/
@Override
   public Integer getBalance() {
       synchronized (this){
           return balance;
       }
   }
   @Override
   public void withdraw(Integer amount) {
       synchronized (this){
           balance -= amount;
       }
   }
  1. 樂觀鎖實現方式,其實樂觀鎖就是無鎖
/**
 * CAS操作 樂觀鎖
 */
class AccountSafeAsAtomic implements Account{
    AtomicInteger balance;
    public AccountSafeAsAtomic(Integer balance) {
        this.balance = new AtomicInteger(balance);
    }
    @Override
    public Integer getBalance() {
        return balance.get();
    }
    @Override
    public void withdraw(Integer amount) {
        while (true){
            int currentValue = balance.get();//當前值
            int nextValue = currentValue - amount;//取錢後的值
	         if(balance.compareAndSet(currentValue,nextValue)){//CAS操作
                break;
            }
        }
    }
}

執行結果:最終結果還剩:0 cost: 198 ms

樂觀鎖和悲觀鎖兩種實現方式結果比較:
(悲觀)最終結果還剩:0 cost: 268 ms (樂觀)最終結果還剩:0 cost: 198 ms
執行多次發現同樣操作情況下樂觀鎖的效率執行更高。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章