數據庫--樂觀鎖與悲觀鎖

樂觀鎖和悲觀鎖是什麼?

  樂觀鎖

      總是假設最好的情況,每次去拿數據的時候都認爲別人不會修改,所以不會上鎖,但在更新的時候會判斷一下數據有沒有被別人更改過。可以使用版本號機制和CAS實現。樂觀鎖用於多讀操作的場景,衝突發生很少。 可以提高吞吐量。

       比如數據庫的write_condition機制 也是一種樂觀鎖。Java中的 java.util.concurrent.atomic 包下面的原子變量類就是使用樂觀鎖的CAS方式實現的。

悲觀鎖

      就跟樂觀鎖相反吧。每次都是先上鎖載做相應操作,就是獨自佔有,讓別人阻塞等待,直到操作完再釋放鎖。共享資源每次只給一個線程使用,其它線程阻塞,用完後再把資源轉讓給其它線程。 

      數據庫中的行鎖、表鎖、讀鎖、寫鎖等都是悲觀鎖,Java中synchronizedReentrantLock等獨佔鎖就是悲觀鎖思想的實現。

 

樂觀鎖常見的實現方式

1.版本號機制 

        一般是在數據表中加上一個數據版本號version字段,表示數據被修改的次數,當數據被修改時,version值會加一。當線程A要更新數據值時,在讀取數據的同時也會讀取version值,在提交更新時,若剛纔讀取到的version值爲當前數據庫中的version值相等時才更新,否則重試更新操作,直到更新成功。

2.CAS

    即compare and swap(比較與交換),是一種有名的無鎖算法。無鎖編程,即不使用鎖的情況下實現多線程之間的變量同步,也就是在沒有線程被阻塞的情況下實現變量的同步,所以也叫非阻塞同步(Non-blocking Synchronization)。CAS算法涉及到三個操作數

  • 需要讀寫的內存值 V
  • 進行比較的值 A
  • 擬寫入的新值 B

當且僅當 V 的值等於 A時,CAS通過原子方式用新值B來更新V的值,否則不會執行任何操作(比較和替換是一個原子操作)。一般情況下是一個自旋操作,即不斷的重試

 

悲觀鎖造成的問題

1.ABA 問題

如果一個變量V初次讀取的時候是A值,並且在準備賦值的時候檢查到它仍然是A值,那我們就能說明它的值沒有被其他線程修改過了嗎?很明顯是不能的,因爲在這段時間它的值可能被改爲其他值,然後又改回A,那CAS操作就會誤認爲它從來沒有被修改過。這個問題被稱爲CAS操作的 "ABA"問題。

JDK 1.5 以後的 AtomicStampedReference 類就提供了此種能力,其中的 compareAndSet 方法就是首先檢查當前引用是否等於預期引用,並且當前標誌是否等於預期標誌,如果全部相等,則以原子方式將該引用和該標誌的值設置爲給定的更新值。

2. 循環時間長開銷大

自旋CAS(也就是不成功就一直循環執行直到成功)如果長時間不成功,會給CPU帶來非常大的執行開銷。 如果JVM能支持處理器提供的pause指令那麼效率會有一定的提升,pause指令有兩個作用,第一它可以延遲流水線執行指令(de-pipeline),使CPU不會消耗過多的執行資源,延遲的時間取決於具體實現的版本,在一些處理器上延遲時間是零。第二它可以避免在退出循環的時候因內存順序衝突(memory order violation)而引起CPU流水線被清空(CPU pipeline flush),從而提高CPU的執行效率。

3.只能保證一個共享變量的原子操作

CAS 只對單個共享變量有效,當操作涉及跨多個共享變量時 CAS 無效。但是從 JDK 1.5開始,提供了AtomicReference類來保證引用對象之間的原子性,你可以把多個變量放在一個對象裏來進行 CAS 操作.所以我們可以使用鎖或者利用AtomicReference類把多個共享變量合併成一個共享變量來操作。

 

synchronized的底層實現主要依靠 Lock-Free 的隊列,基本思路是 自旋後阻塞競爭切換後繼續競爭鎖稍微犧牲了公平性,但獲得了高吞吐量

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章