樂觀鎖以及悲觀鎖


悲觀鎖

       假設是每次獲取數據都認為會被其他線程修改,每次來操作數據(可讀、可寫)的時候都會加鎖;悲觀鎖的實現是Synchronized。

悲觀鎖存在問題:

1、多線程競爭,引起性能問題 (鎖的釋放和獲取需要上下文切換和調度等)

2、一個線程獲取鎖會導致其他線程被掛起(阻塞)

樂觀鎖

       假設對數據的操作一般都不會發生沖突,讀取操作時,不會加鎖,在對數據進行變更操作是,才來檢測當前的數據是否發送沖突,發生沖突返回錯誤信息,讓用戶類決定如何做樂觀鎖闡述的思路,主要有兩個步驟操作:沖突檢測和數據更新,該方式的實現就是CAS是樂觀鎖。

CAS

      CAS(Compare And Set),多個線程通過CAS嘗試修改同一個變量,只有一個線程在同一時刻進行修改,而其他的操作失敗,失敗的線程不會掛起,告訴失敗的線程可以再次嘗試。

      CAS操作涉及到三個操作數:需要讀寫的內存位置(V)、進行比較的預期的原值(A)、待寫入的新值(B)。

 

    第一步:獲取位置V的值A   

    第二步:將A和B同時進行處理,將A和位置V存儲值進行比較,如果相等,則將位置V的值由A變更為B,則操作成功;不相等,則不變更為B,繼續循環進入第一步。

    如果內存位置V的值與預期原值A相匹配,那么處理器會自動將該位置值更新為新值B.否則處理器不做任何操作。無論哪種情況,它都會在CAS指令之前返回該位置的值。(在 CAS的一些特殊情況下將僅返回CAS是否成功,而不提取當前值。) CAS有效地說明了“我認為位置V應該包含值A;如果包含該值,則將B放到這個位置;否則,不要更改該位置,只告訴我這個位置現在的值即可。

 

CAS引發的ABA問題

假設存在以下線程操作序列:

1、線程1從內存位置V中取得A

2、線程2從內存位置V獲取A

3、線程2進行一些操作,將A修改為其他的結果,將B修改為A

4、線程2將A再次寫入到位置V

5、線程1進行CAS操作,發現位置V中任然是A,直接修改為B,操作成功

6、盡管線程1操作成功,但並不該表該過程沒有問題,對於線程1而言,線程2 的修改以導致數據丟失

舉例說明ABA問題:

1、現有一個單向鏈表實現的堆棧,棧頂為A,線程1獲取到A.next為B,線程1希望通過CAS操作將棧頂替換為B

2、在線程1執行CAS操作之前,線程2來執行,將A、B出站,在依次入棧D、C、A,而對象B處於游離狀態

3、此時線程1執行CAS操作,檢測棧頂為A,CAS成功執行,棧頂為B,實際是B.next = null,此時堆棧只有一個B,C和D組成的鏈表不在堆棧中,C\D 被丟棄了

ABA問題的解決方案

ABA問題的解決需要使用版本號,在變量前加上版本號,每次變量的變更操作版本號+1,那么A-B-A就變成1A-2B-3A。

使用CAS會引發的問題

CAS雖然比Java中提供的鎖的開銷小,但是存在問題

  1. ABA問題 ABA問題通過版本號解決
  2. 循環時間長開銷大     自旋CAS如果長時間不成功,會給CPU帶來非常大的執行開銷。如果JVM能支持處理器提供的pause指令那么效率會有一定的提升,pause指令有兩個作用,第一它可以延遲流水線執行指令(de-pipeline),使CPU不會消耗過多的執行資源,延遲的時間取決於具體實現的版本,在一些處理器上延遲時間是零。第二它可以避免在退出循環的時候因內存順序沖突(memory order violation)而引起CPU流水線被清空(CPU pipeline flush),從而提高CPU的執行效率。
  3. 只能保證一個共享變量的原子操作     當對一個共享變量執行操作時,我們可以使用循環CAS的方式來保證原子操作,但是對多個共享變量操作時,循環CAS就無法保證操作的原子性,這個時候就可以用鎖,或者有一個取巧的辦法,就是把多個共享變量合並成一個共享變量來操作。比如有兩個共享變量i=2,j=a,合並一下ij=2a,然后用CAS來操作ij。從Java1.5開始JDK提供了AtomicReference類來保證引用對象之間的原子性,你可以把多個變量放在一個對象里來進行CAS操作


免責聲明!

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



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