五分鐘學會悲觀樂觀鎖-java vs mysql vs redis三種實現

  • 2019 年 10 月 3 日
  • 筆記

1 悲觀鎖樂觀鎖簡介

樂觀鎖( Optimistic Locking ) 相對悲觀鎖而言,樂觀鎖假設認為數據一般情況下不會造成衝突,所以在數據進行提交更新的時候,才會正式對數據的衝突與否進行檢測,如果發現衝突了,則讓返回用戶錯誤的資訊,讓用戶決定如何去做。

悲觀鎖,正如其名,它指的是對數據被外界(包括本系統當前的其他事務,以及來自外部系統的事務處理)修改持保守態度,因此,在整個數據處理過程中,將數據處於鎖定狀態。(百科)

最形象的悲觀鎖 vs 樂觀鎖

五分鐘學會悲觀樂觀鎖-java  vs mysql vs redis三種實現

 

2.悲觀鎖樂觀鎖使用場景

兩種鎖各有優缺點,不能單純的定義哪個好於哪個。樂觀鎖比較適合數據修改比較少,讀取比較頻繁的場景,即使出現了少量的衝突,這樣也省去了大量的鎖的開銷,故而提高了系統的吞吐量。但是如果經常發生衝突(寫數據比較多的情況下),上層應用不不斷的retry,這樣反而降低了性能,對於這種情況使用悲觀鎖就更合適。

3.Java中悲觀樂觀鎖實現

樂觀鎖:java中的樂觀鎖基本都是通過CAS操作實現的,CAS是一種更新的原子操作,比較當前值跟傳入值是否一樣,一樣則更新,否則失敗。以 java.util.concurrent 中的 AtomicInteger 為例,該類中原子操作保證了執行緒訪問的準確性。

getAndIncrement():獲取數據

import java.util.concurrent.atomic.AtomicInteger;  public class JavaAtomic {   public static void main(String[] args) throws InterruptedException {   ProcessingThread pt = new ProcessingThread();   Thread t1 = new Thread(pt, "t1");   t1.start();   Thread t2 = new Thread(pt, "t2");   t2.start();   t1.join();   t2.join();   System.out.println("Processing count=" + pt.getCount());   }  }  class ProcessingThread implements Runnable {   private AtomicInteger count = new AtomicInteger();   @Override   public void run() {   for (int i = 1; i < 5; i++) {   processSomething(i);   count.incrementAndGet();   }   }   public int getCount() {   return this.count.get();   }   private void processSomething(int i) {   // processing some job   try {   Thread.sleep(i * 1000);   } catch (InterruptedException e) {   e.printStackTrace();   }   }  }

 

compareAndSet(int expect, int update): 更新數據

import java.util.concurrent.atomic.AtomicInteger;    public class Main  {   public static void main(String[] args)   {   AtomicInteger atomicInteger = new AtomicInteger(100);     boolean isSuccess = atomicInteger.compareAndSet(100,110); //current value 100     System.out.println(isSuccess); //true     isSuccess = atomicInteger.compareAndSet(100,120); //current value 110     System.out.println(isSuccess); //false     }  }

 

利用JNI(Java Native Interface)來完成CPU指令的操作,訪問暫存器記憶體數據進行數據訪問和設置

悲觀鎖:java中的悲觀鎖就是Synchronized,如單例模式所示

public class SingletonDemo {   private static SingletonDemo instance = null;     private SingletonDemo() { }     public static synchronized SingletonDemo getInstance() {   if (instance == null) {   instance = new SingletonDemo ();   }   return instance;   }  }

 

樂觀鎖+悲觀鎖:AQS框架下的鎖則是先嘗試cas樂觀鎖去獲取鎖,獲取不到,才會轉換為悲觀鎖,如RetreenLock【http://ifeve.com/reentrantlock-and-fairness/】

public class ReentrantLockTest {      private static Lock fairLock = new ReentrantLock(true);      private static Lock unfairLock = new ReentrantLock();      @Test      public void fair() {          System.out.println("fair version");          for (int i = 0; i < 5; i++) {              Thread thread = new Thread(new Job(fairLock));              thread.setName("" + i);              thread.start();          }          try {              Thread.sleep(5000);          } catch (InterruptedException e) {              e.printStackTrace();          }      }      @Test      public void unfair() {          System.out.println("unfair version");          for (int i = 0; i < 5; i++) {              Thread thread = new Thread(new Job(unfairLock));              thread.setName("" + i);              thread.start();          }          try {              Thread.sleep(5000);          } catch (InterruptedException e) {              e.printStackTrace();          }      }      private static class Job implements Runnable {          private Lock lock;          public Job(Lock lock) {              this.lock = lock;          }          @Override          public void run() {              for (int i = 0; i < 5; i++) {                  lock.lock();                  try {                      System.out.println("Lock by:"                              + Thread.currentThread().getName());                  } finally {                      lock.unlock();                  }              }          }      }  }

 

4 資料庫悲觀鎖樂觀鎖的實現(以mysql為例)

悲觀鎖,使用事務實現

//0.開始事務  begin;/begin work;/start transaction; (三者選一就可以)  //1.查詢出商品資訊  select status from t_goods where id=1 for update;  //2.根據商品資訊生成訂單  insert into t_orders (id,goods_id) values (null,1);  //3.修改商品status為2  update t_goods set status=2;  //4.提交事務  commit;/commit work;

 

樂觀鎖

1.使用數據版本(Version)記錄機制實現

五分鐘學會悲觀樂觀鎖-java  vs mysql vs redis三種實現

 

2.樂觀鎖定的第二種實現方式和第一種差不多,同樣是在需要樂觀鎖控制的table中增加一個欄位,名稱無所謂,欄位類型使用時間戳(timestamp), 和上面的version類似

5 nosql 悲觀鎖樂觀鎖的實現(以redis為例)

樂觀鎖使用watch

五分鐘學會悲觀樂觀鎖-java  vs mysql vs redis三種實現

 

悲觀鎖使用事務

> MULTI  OK  > INCR foo  QUEUED  > INCR bar  QUEUED  > EXEC  1) (integer) 1  2) (integer) 1

 

6 總結

樂觀鎖機制採取了更加寬鬆的加鎖機制。悲觀鎖大多數情況下依靠資料庫的鎖機制實現,以保證操作最大程度的獨佔性。但隨之而來的就是資料庫 性能的大量開銷,特別是對長事務而言,這樣的開銷往往無法承受。相對悲觀鎖而言,樂觀鎖更傾向於開發運用。【百科】

參考資料

【1】https://chenzhou123520.iteye.com/blog/1860954

【2】https://chenzhou123520.iteye.com/blog/1863407

【3】https://blog.csdn.net/skycnlr/article/details/85689582

【4】https://www.journaldev.com/1095/atomicinteger-java

【5】https://howtodoinjava.com/java/multi-threading/atomicinteger-example/

【6】https://developpaper.com/transaction-mechanism-and-optimistic-lock-implementation-in-redis/