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


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/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM