CAS提供的比較-交換的原子性操作為實現其他更復雜的特性提供了底層支持,JDK或其他框架中對CAS使用場景和實例非常普遍。
如果在代碼層面實現比較-交換的,在函數層面可能會採用如下邏輯:
上述操作在單線程環境下執行不會存在問題,而在多線程環境下,由於線程調度發生線程切換,則本來順序執行的語句發生中斷,會導致CAS失敗。
在語言級別無法保證CAS操作的原子性,需要底層硬件指令的支持。底層硬件通過將 CAS 裡的多個操作在硬件層面語義實現上,通過一條處理器指令保證了原子性操作,例如如下指令:
- 測試並設置(Tetst-and-Set)
- 獲取並增加(Fetch-and-Increment)
- 交換(Swap)
- 比較並交換(Compare-and-Swap)
- 加載鏈接/條件存儲(Load-Linked/Store-Conditional)
比如在x86 指令集中有 cmpxchg 指令完成 CAS 功能。
JDK中通過Unsafe類對CAS提供了三個本地方法提供CAS支持:
- 針對對象的比較交換
- 針對int型比較交換
- 針對long型比較交換
- ......
ABA問題 CAS無法解決的問題中最明顯的就是"A-B-A"問題,如下圖所示:
由於CAS是通過比較內存地址的值是否與舊值相同進行賦值條件判定的,所以如果初始值發生從A到B再到A的變化,CAS無法識別中間狀態,只判定最終值相等。A-B-A問題的解決一般是通過引入版本機制進行版本標識,每次對值進行修改都會產生新的版本號。
自旋CAS引起的CPU空耗當舊值比對不一致時CAS操作通過不斷重試來保證最終成功,如果自旋CAS如果長時間不成功,會給CPU帶來非常大的執行開銷。
<code>do { 備份舊數據; 基於舊數據構造新數據; }while(!CAS( V,E,U ))/<code>
如果JVM能支持處理器提供的pause指令那麼效率會有一定的提升,pause指令有兩個作用,第一它可以延遲流水線執行指令(de-pipeline),使CPU不會消耗過多的執行資源,延遲的時間取決於具體實現的版本,在一些處理器上延遲時間是零。第二它可以避免在退出循環的時候因內存順序衝突(memory order violation)而引起CPU流水線被清空(CPU pipeline flush),從而提高CPU的執行效率。
CAS只能解決單個變量的原子操作CAS的方式可以保證單個變量的原子操作,無法保證多個共享變量情況下的原子性。JDK提供了AtomicReference類來保證引用對象之間的原子性,你可以把多個變量放在一個對象裡來進行CAS操作。
CAS對原子性操作的支持為實現更多其他特性提供了強有力的支持,更多的瞭解CAS的使用可以參考JDK中源碼。