漫談CAS那些事兒

CAS提供的比較-交換的原子性操作為實現其他更復雜的特性提供了底層支持,JDK或其他框架中對CAS使用場景和實例非常普遍。

漫談CAS那些事兒

如果在代碼層面實現比較-交換的,在函數層面可能會採用如下邏輯:

漫談CAS那些事兒

上述操作在單線程環境下執行不會存在問題,而在多線程環境下,由於線程調度發生線程切換,則本來順序執行的語句發生中斷,會導致CAS失敗。

漫談CAS那些事兒

在語言級別無法保證CAS操作的原子性,需要底層硬件指令的支持。底層硬件通過將 CAS 裡的多個操作在硬件層面語義實現上,通過一條處理器指令保證了原子性操作,例如如下指令:

  • 測試並設置(Tetst-and-Set)
  • 獲取並增加(Fetch-and-Increment)
  • 交換(Swap)
  • 比較並交換(Compare-and-Swap)
  • 加載鏈接/條件存儲(Load-Linked/Store-Conditional)

比如在x86 指令集中有 cmpxchg 指令完成 CAS 功能。

漫談CAS那些事兒

JDK中通過Unsafe類對CAS提供了三個本地方法提供CAS支持:

漫談CAS那些事兒

  • 針對對象的比較交換
  • 針對int型比較交換
  • 針對long型比較交換
  • ......

ABA問題 CAS無法解決的問題中最明顯的就是"A-B-A"問題,如下圖所示:

漫談CAS那些事兒

由於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對原子性操作的支持為實現更多其他特性提供了強有力的支持,更多的瞭解CAS的使用可以參考JDK中源碼。


分享到:


相關文章: