美團終面:你確定CAS不加鎖嗎?
CAS大家都知道,這是一項樂觀鎖技術,是Compare And Swap的簡稱,顧名思義就是先比較再替換。
雖然他叫樂觀鎖,但是我們都知道它是不需要加鎖的,在JDK1.5 中的JUC就是建立在CAS之上的。相對于synchronized這種阻塞算法,CAS是非阻塞算法的一種常見實現。所以J.U.C在性能上有了很大的提升。
我們以java.util.concurrent中的AtomicInteger為例,看一下在不使用鎖的情況下是如何保證線程安全的。主要理解getAndIncrement方法,該方法的作用相當于 ++i 操作:
getAndIncrement采用了CAS操作,每次從內存中讀取數據然后將此數據和+1后的結果進行CAS操作,如果成功就返回結果,否則重試直到成功為止。而compareAndSet利用unsafe的compareAndSwapInt方法實現的。
啥是Unsafe呢?
Unsafe是CAS的核心類。因為Java無法直接訪問底層操作系統,而是通過本地(native)方法來訪問。不過盡管如此,JVM還是開了一個后門,JDK中有一個類Unsafe,它提供了硬件級別的原子操作。
Unsafe是Java中一個底層類,包含了很多基礎的操作,比如數組操作、對象操作、內存操作、CAS操作、線程(park)操作、柵欄(Fence)操作,JUC包、一些三方框架都使用Unsafe類來保證并發安全。
Unsafe類提供了硬件級別的原子操作,如CAS原子操作。
?但是,大家有沒有想過這樣的問題:
硬件層面CAS又是如何保證原子性的呢?真的完全沒加鎖嗎?
拿比較常見的x86架構的CPU來說,其實 CAS 操作通常使用 cmpxchg 指令實現的。
可是為啥cmpxchg 指令能保證原子性呢?主要是有以下幾個方面的保障:
1. cmpxchg 指令是一條原子指令。在 CPU 執行 cmpxchg 指令時,處理器會自動鎖定總線,防止其他 CPU 訪問共享變量,然后執行比較和交換操作,最后釋放總線。
2. cmpxchg 指令在執行期間,CPU 會自動禁止中斷。這樣可以確保 CAS 操作的原子性,避免中斷或其他干擾對操作的影響。
3. cmpxchg 指令是硬件實現的,可以保證其原子性和正確性。CPU 中的硬件電路確保了 cmpxchg 指令的正確執行,以及對共享變量的訪問是原子的。
所以,在操作系統層面,CAS還是會加鎖的,通過加鎖的方式鎖定總線,避免其他CPU訪問共享變量。
所以,解決并發問題,歸根結底還得靠鎖!