悲觀鎖、樂觀鎖

 

悲觀鎖(Pessimistic Lock)

顧名思義,就是很悲觀,每次去拿數據的時候都認爲別人會修改,因此每次在拿數據的時候都會上鎖,這樣別人想拿這個數據就會block直到它拿到鎖。傳統的關係型數據庫裏邊就用到了不少這種鎖機制,好比行鎖,表鎖等,讀鎖,寫鎖等,都是在作操做以前先上鎖。java

咱們認爲系統中的併發更新會很是頻繁,而且事務失敗了之後重來的開銷很大,這樣以來,咱們就須要採用真正意義上的鎖來進行實現。悲觀鎖的基本思想就是每次一個事務讀取某一條記錄後,就會把這條記錄鎖住,這樣其它的事務要想更新,必須等之前的事務提交或者回滾解除鎖。算法

實現方式:

大多在數據庫層面實現加鎖操做,JDBC方式:在JDBC中使用悲觀鎖,須要使用select for update語句,e.g.sql

<code class="language-sql hljs ">Select * from Account 
where ...(where condition).. for update</code>

 

樂觀鎖(Optimistic Lock)

顧名思義,就是很樂觀,每次去拿數據的時候都認爲別人不會修改,因此不會上鎖,可是在更新的時候會判斷一下在此期間別人有沒有去更新這個數據,可使用版本號等機制。樂觀鎖適用於多讀的應用類型,這樣能夠提升吞吐量,像數據庫若是提供相似於write_condition機制的其實都是提供的樂觀鎖。數據庫

咱們認爲系統中的事務併發更新不會很頻繁,即便衝突了也沒事,大不了從新再來一次。它的基本思想就是每次提交一個事務更新時,咱們想看看要修改的東西從上次讀取之後有沒有被其它事務修改過,若是修改過,那麼更新就會失敗。數據結構

實現方式:

大可能是基於數據版本(Version)記錄機制實現,何謂數據版本?即爲數據增長一個版本標識,在基於數據庫表的版本解決方案中,通常是經過爲數據庫表增長一個 「version」 字段來實現。併發

讀取出數據時,將此版本號一同讀出,以後更新時,對此版本號加一。此時,將提 交數據的版本數據與數據庫表對應記錄的當前版本信息進行比對,若是提交的數據 版本號大於數據庫表當前版本號,則予以更新,不然認爲是過時數據。
假如系統中有一個Account的實體類,咱們在Account中多加一個version字段,那麼咱們JDBC Sql語句將以下寫:
性能

<code class="language-sql hljs "><code class="language-sql hljs ">Select a.version....from Account as a 
where (where condition..)
 
Update Account set version = version+1.....(another field) 
where version =?...(another contidition)</code></code>

這樣以來咱們就能夠經過更新結果的行數來進行判斷,若是更新結果的行數爲0,那麼說明實體從加載以來已經被其它事務更改了,因此就拋出自定義的樂觀鎖定異常。具體實例以下:spa

<code class="language-sql hljs "><code class="language-sql hljs "><code class="language-java hljs ">int rowsUpdated = statement.executeUpdate(sql);
if (rowsUpdated ==0 ) {
    throws new OptimisticLockingFailureException();
}</code></code></code>

 

 

典型的樂觀鎖:基於衝突檢測的樂觀鎖(CAS自旋)

Synchronized互斥鎖屬於悲觀鎖,它有一個明顯的缺點,它無論數據存不存在競爭都加鎖,隨着併發量增長,且若是鎖的時間比較長,其性能開銷將會變得很大。有沒有辦法解決這個問題?答案就是基於衝突檢測的樂觀鎖。這種模式下,已經沒有所謂的鎖概念了,每條線程都直接先去執行操做,計算完成後檢測是否與其餘線程存在共享數據競爭,若是沒有則讓此操做成功,若是存在共享數據競爭則可能不斷地從新執行操做和檢測,直到成功爲止,這種叫作CAS自旋.net

 

CAS(Compare And Swap)比較並轉換 
算法涉及三個數:內存地址V,舊的預期值A,新的預期值B。當且僅當舊的預期值A和內存值V相同時,將內存值改成B,不然什麼也不作。 上述的處理過程是一個原子操做(靠硬件來保證)。
如何來理解上面這一段話呢?咱們先了解一下樂觀鎖和悲觀鎖各自的作事方式,首先,悲觀鎖的態度是一件事情我必需要能百分之百掌控才能去作,不然就認爲這件事情必定會出問題,而樂觀鎖的態度就是無論什麼事情,我都會先嚐試去作,大不了最後不成功就是了。 
基於CAS的自旋就是典型的樂觀鎖,程序執行時,線程1從共享內存中取值V並建一個副本A,對A進行計算後將新的值保存爲B,而後對A值和內存中的V值進行比較,若是A等於V,則認爲內存中的V值沒有被其餘線程修改過,能夠將新值B賦給內存,不然,認爲內存中已被其餘的線程修改,則從新執行計算操做和檢測,知道舊的指望值A等於內存值V爲止。 
Java併發包java.util.concurrent.*的核心就是CAS自旋原理。如AtomicInteger、AtomicLong等都是基於CAS實現的。線程

 

兩種鎖的比較

兩種鎖各有優缺點,不可認爲一種好於另外一種,像樂觀鎖適用於寫比較少的狀況下,即衝突真的不多發生的時候,這樣能夠省去了鎖的開銷,加大了系統的整個吞吐量。但若是常常產生衝突,上層應用會不斷的進行retry,這樣反卻是下降了性能,因此這種狀況下用悲觀鎖就比較合適。

 

樂觀鎖是假定讀取的數據,在寫以前不會被更新。適用於數據更新不頻繁的場景。

相反,當數據更新頻繁的時候,樂觀鎖的效率很低,由於基本上每次寫的時候都要重複讀寫兩次以上。

  1. 對於數據更新頻繁的場合,悲觀鎖效率更高 ;

  2. 對於數據更新不頻繁的場合,樂觀鎖效率更高;

通常來講若是併發量很高的話,建議使用悲觀鎖,不然的話就使用樂觀鎖。

若是併發量很高時使用樂觀鎖的話,會致使不少的併發事務回滾、操做失敗。

相關文章
相關標籤/搜索