悲觀鎖(Pessimistic Lock)
顧名思義,就是很悲觀,每次去拿資料的時候都認為别人會修改,是以每次在拿資料的時候都會上鎖,這樣别人想拿這個資料就會block直到它拿到鎖。傳統的關系型資料庫裡邊就用到了很多這種鎖機制,比如行鎖,表鎖等,讀鎖,寫鎖等,都是在做操作之前先上鎖。
我們認為系統中的并發更新會非常頻繁,并且事務失敗了以後重來的開銷很大,這樣以來,我們就需要采用真正意義上的鎖來進行實作。悲觀鎖的基本思想就是每次一個事務讀取某一條記錄後,就會把這條記錄鎖住,這樣其它的事務要想更新,必須等以前的事務送出或者復原解除鎖。
實作方式:
大多在資料庫層面實作加鎖操作,JDBC方式:在JDBC中使用悲觀鎖,需要使用select for update語句,e.g.
<code class="language-sql hljs ">Select * from Account
where ...(where condition).. for update</code>
樂觀鎖(Optimistic Lock)
樂觀鎖(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,那麼說明實體從加載以來已經被其它事務更改了,是以就抛出自定義的樂觀鎖定異常。具體執行個體如下:
<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自旋。
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,這樣反倒是降低了性能,是以這種情況下用悲觀鎖就比較合适。
樂觀鎖是假定讀取的資料,在寫之前不會被更新。适用于資料更新不頻繁的場景。
相反,當資料更新頻繁的時候,樂觀鎖的效率很低,因為基本上每次寫的時候都要重複讀寫兩次以上。
- 對于資料更新頻繁的場合,悲觀鎖效率更高 ;
- 對于資料更新不頻繁的場合,樂觀鎖效率更高;
一般來說如果并發量很高的話,建議使用悲觀鎖,否則的話就使用樂觀鎖。
如果并發量很高時使用樂觀鎖的話,會導緻很多的并發事務復原、操作失敗。