cas全稱 compare and swap(比較與交換),是一種無鎖演算法。在不使用鎖(沒有執行緒被阻塞)的情況下實現多執行緒之間的變數同步。
j**a.util.concurrent包中的原子類就是通過cas來實現了樂觀鎖。
cas演算法涉及到三個運算元:
當且僅當 v 的值等於 a 時,cas通過原子方式用新值b來更新v的值(「比較+更新」整體是乙個原子操作),否則不會執行任何操作。一般情況下,「更新」是乙個不斷重試的操作。
之前提到j**a.util.concurrent包中的原子類,就是通過cas來實現了樂觀鎖,那麼我們進入原子類atomicinteger的原始碼,看一下atomicinteger的定義:
根據定義我們可以看出各屬性的作用:
接下來,我們檢視atomicinteger的自增函式incrementandget()的原始碼時,發現自增函式底層呼叫的是unsafe.getandaddint()。但是由於jdk本身只有unsafe.class,只通過class檔案中的引數名,並不能很好的了解方法的作用,所以我們通過openjdk 8 來檢視unsafe的原始碼:
根據openjdk 8的原始碼我們可以看出,getandaddint()迴圈獲取給定物件o中的偏移量處的值v,然後判斷記憶體值是否等於v。如果相等則將記憶體值設定為 v + delta,否則返回false,繼續迴圈進行重試,直到設定成功才能退出迴圈,並且將舊值返回。整個「比較+更新」操作封裝在compareandswapint()中,在jni裡是借助於乙個cpu指令完成的,屬於原子操作,可以保證多個執行緒都能夠看到同乙個變數的修改值。
後續jdk通過cpu的cmpxchg指令,去比較暫存器中的 a 和 記憶體中的值 v。如果相等,就把要寫入的新值 b 存入記憶體中。如果不相等,就將記憶體值 v 賦值給暫存器中的值 a。然後通過j**a**中的while迴圈再次呼叫cmpxchg指令進行重試,直到設定成功為止。
cas雖然很高效,但是它也存在三大問題,這裡也簡單說一下:
1、aba問題
cas需要在操作值的時候檢查記憶體值是否發生變化,沒有發生變化才會更新記憶體值。但是如果記憶體值原來是a,後來變成了b,然後又變成了a,那麼cas進行檢查時會發現值沒有發生變化,但是實際上是有變化的。aba問題的解決思路就是在變數前面新增版本號,每次變數更新的時候都把版本號加一,這樣變化過程就從「a-b-a」變成了「1a-2b-3a」。
jdk從1.5開始提供了atomicstampedreference類來解決aba問題,具體操作封裝在compareandset()
中。compareandset()
首先檢查當前引用和當前標誌與預期引用和預期標誌是否相等,如果都相等,則以原子方式將引用值和標誌的值設定為給定的更新值。
2、開銷過大
迴圈時間長開銷大。cas操作如果長時間不成功,會導致其一直自旋,給cpu帶來非常大的開銷。
3、原子性問題
只能保證乙個共享變數的原子操作。對乙個共享變數執行操作時,cas能夠保證原子操作,但是對多個共享變數操作時,cas是無法保證操作的原子性的。
j**a從1.5開始jdk提供了atomicreference
類來保證引用物件之間的原子性,可以把多個變數放在乙個物件裡來進行cas操作。
CAS 樂觀鎖策略
cas,即compare and swap,比較後再交換,使用的場景 執行緒1執行如下指令 read a a write a 執行緒2執行如下指令 read a a write a 此時有這樣的執行順序 此時如果a的初始值為0,那麼兩個執行緒執行完最後的結果為1,而不是2。如下 public fin...
樂觀鎖與悲觀鎖 及CAS無鎖演算法
樂觀鎖與悲觀鎖 獨佔鎖是一種悲觀鎖,synchronized就是一種獨佔鎖,它假設壞的情況,並且只有在確保其它執行緒不會造成干擾 的情況下執行,會導致其它所有需要鎖的執行緒掛起,等待持有鎖的執行緒釋放鎖。而另乙個更加有效的鎖就 是樂觀 鎖。所謂樂觀鎖就是,每次不加鎖而是假設沒有衝突而去完成某項操作,...
樂觀鎖與CAS操作
atomicinteger來研究在沒有鎖的情況下是如何做到資料正確性的?這裡舉例說明乙個方法 getandincrement public final int getandincrement 這裡面有個compareandset方法,其實是jni呼叫,在這裡採用了cas操作,每次從記憶體中讀取資料然...