天天看點

面試題:什麼是悲觀鎖 & 樂觀鎖?

悲觀鎖有 & 樂觀鎖

首先,悲觀鎖與樂觀鎖是根據操作時是否鎖住資源來判别的。悲觀鎖擷取到鎖時,必須要鎖住資源;樂觀鎖則不會。一開始兩線程争搶鎖:

面試題:什麼是悲觀鎖 & 樂觀鎖?

悲觀鎖

悲觀鎖之是以悲觀,那是因為它覺得如果不鎖住這個資源,别的線程就會來争搶,造成資料結果錯誤,是以悲觀鎖為了確定結果的正确性,會在每次擷取并修改資料時,都把資料鎖住,讓其他線程無法通路該資料,這樣就可以確定資料内容萬無一失,從這點看悲觀鎖特别穩。

下面通過幾張圖,大概就能明白悲觀鎖的執行過程了:

接上面場景,如果 A 拿到鎖,正在操作資源,B 就隻能進入等待。

面試題:什麼是悲觀鎖 & 樂觀鎖?

直至 A 執行完畢釋放鎖,CPU 喚醒等待此鎖的線程 B。

面試題:什麼是悲觀鎖 & 樂觀鎖?

線程 B 擷取到了鎖,就可以對同步資源進行自己的操作。這就是悲觀鎖的操作流程。

面試題:什麼是悲觀鎖 & 樂觀鎖?

樂觀鎖

樂觀鎖顧名思義,比較樂觀。相比于悲觀鎖,它是不鎖住資源的,因為它覺得自己在操作資源時并不會有其他線程幹擾。

是以,為了保障資料的正确性。它在操作之前,會先判斷在自己操作期間,其他線程是否有操作。如果沒有,直接操作;如果有,則根據業務選擇報錯或者重試。

下面來看看,樂觀鎖的執行過程:

樂觀鎖的這把鎖,其實就是依賴的 CAS (compare and swap:比較并交換)算法。是以,它在操作資源之前并不需要獲得鎖,直接讀取資源到自己的工作記憶體内操作:

面試題:什麼是悲觀鎖 & 樂觀鎖?

操作完成,準備更新資源時。就會觸發 CAS 算法,判斷資源是否被其他線程修改過。

面試題:什麼是悲觀鎖 & 樂觀鎖?

沒有修改過,直接更新,線程執行完畢。

面試題:什麼是悲觀鎖 & 樂觀鎖?

被修改過,根據業務邏輯走下一步,是重試還是報錯?

面試題:什麼是悲觀鎖 & 樂觀鎖?

典型應用

值得注意的是,不管是在 Java 還是資料庫中都用到了。悲觀鎖、樂觀鎖的概念,隻是實作方式稍有不同。下面介紹下 Java 中的悲觀、樂觀鎖:

  • 悲觀鎖:synchronized 關鍵字和 Lock 接口

這兩夠經典的,synchronized 必須要擷取 mintor 鎖才能進去操作資源;Lock 接口也是,必須顯示調用 lock 才能操作資源。必須取到鎖才能進行操作,這就是悲觀鎖的思想。

  • 樂觀鎖:原子類

這類應該很常用,比如用作線程間的計數器。典型如 AtomicInteger 類在進行運算時,就使用了樂觀鎖的思想。使用 compareAndSet 方法更新資料,更新失敗則重試。

資料庫中的悲觀、樂觀鎖:

  • 典型的 select for update 語句,用的就是悲觀鎖思想。在送出之前不允許第三方來修改該資料。高并發環境吃不消。
  • 利用 version 字段實作樂觀鎖,version 代表這條資料的版本。操作資料不需要擷取鎖,操作完準備更新時。對比版本号是不是和擷取資料時一緻?是:更新,否:重新計算,再嘗試更新。

比如以下的 update 語句:

UPDATE peopleSETname = '狗哥',    version = 2WHERE id = 30624700AND version = 1
           

使用場景

說了這麼久,悲觀鎖樂觀鎖的差別我知道了。那這兩種鎖在啥樣的場景下使用呢?

有人說悲觀鎖比樂觀鎖消耗大,因為悲觀要鎖、樂觀不要鎖(注意,這裡我說不要是實際沒鎖住資源,它的鎖其實是 CAS 算法)。是的,如果并發量很小的情況下,悲觀鎖确實比樂觀鎖消耗大。但如果并發量很高,導緻樂觀鎖一直在重試,這時它消耗的資源比固定開銷的悲觀大,也是說不定的。

  • 悲觀鎖适用于并發寫入多,競争激烈等場景,這些場景下,悲觀鎖确實會讓得不到鎖的線程阻塞,但這些開銷是固定的。它可以避免後面更新時的無用反複嘗試操作,節約開銷。
  • 樂觀鎖适用于大部分是讀取,少部分是修改的場景,也适合雖然讀寫都很多,但是并發并不激烈的場景。在這些場景下,樂觀鎖不加鎖的特點能讓性能大幅提高。

繼續閱讀