為什么CAS加鎖是線程安全的?——原子操作底層原理


引入

  • 在Java中實現並發很多時候我們都是使用synchronized關鍵字,自從jdk1.6對synchronized進行重大優化后,其性能問題得到了改善,與ReentrankLock相比性能方面相差無幾

  • 性能的改善得益於偏向鎖、輕量級鎖的引入,它們具體的實現方式可參考《Java並發編程的藝術》和《深入理解Java虛擬機》這兩本書。偏向鎖、輕量級鎖和重量級鎖不同的地方在於不是通過信號量機制(強制阻塞)而是通過自旋CAS實現互斥訪問的,避免了強制阻塞時用戶態與核心態之間切換帶來的開銷(系統調用),這里的開銷主要是保存用戶態的上下文信息。

自旋CAS

  • CAS操作是一個原子操作,所謂原子操作就是指在執行期間不會被其他線程打斷,要么執行完畢,要么不執行

  • CAS操作有三個操作數V(內存地址),A(舊的預期值),B(准備設置的新值)。指令執行時先看V指向的內存中存儲的值是否和A相同,如果相同才會更新為B,否則什么也不做。

  • 自旋是指當對象或同步塊已經被其他線程鎖定時,競爭線程空轉等待占用線程執行完畢的情形。注意此時競爭線程並沒有阻塞,而是原地空轉,執行一個無限循環判斷對象是否已解鎖,所以不存在用戶態到核心態的轉換,因而同步效率較高(但會占用CPU時間)。

從問題出發理解原子性

  • 我一直有個疑惑,CAS的原子性和使用CAS加鎖保證線程安全有什么關系?假設有多個線程同時在對同一塊內存進行CAS操作的話,那不就有可能出問題嗎:兩個線程T1,T2同時對同一對象執行CAS操作加鎖,V存儲同一塊內存地址,A當然也是同樣舊的預期值,那么這種情況下T1和T2都可以進行更新,那么CAS操作加鎖過程就是無效的,因為CAS操作成功后線程就會進入同步塊,此時就會有多個線程同時執行同步塊中的代碼······那這不就會使同步塊線程不安全了嗎。

  • 后來我明白了CAS原子性和線程安全的關系,在多個線程同時CAS的情況下是不會發生多個線程CAS成功的情況的,因為計算機底層實現保證了V指向內存的互斥性和立即可見性,可以理解為CAS操作是底層保證的線程安全

  • 首先說結論,一個線程T在CAS操作時,其他線程無法訪問V指向的內存地址,並且一旦T更新了V指向內存中的值,其他所有線程的V指向內存都變得無效。

  • 處理器實現原子操作有兩種做法

    • 一是總線鎖,在多CPU 下,當其中一個處理器要對共享內存進行操作的時候,在總線上發出一個 LOCK# 信號,這個信號使得其他處理器無法通過總線來訪問到共享內存中的數據

    • 二是緩存鎖,如果共享內存已經被緩存,那么鎖總線沒有意義。緩存鎖核心是使用了緩存一致性協議,如MESI協議

      • MSEI表示緩存行的四種狀態

        • M(Modify) 表示共享數據只緩存在當前 CPU 緩存中, 並且是被修改狀態,也就是緩存的數據和主內存中的數據不一致

        • E(Exclusive) 表示緩存的獨占狀態,數據只緩存在當前 CPU 緩存中,並且沒有被修改

        • S(Shared) 表示數據可能被多個 CPU 緩存,並且各個緩存中的數據和主內存數據一致

        • I(Invalid) 表示緩存已經失效

      • MESI 協議中,每個緩存的緩存控制器不僅知道自己的 讀寫操作,而且也監聽(snoop)其它 Cache 的讀寫操作

      • CPU在讀數據時,如果緩存行狀態是I,則需要從內存中讀取,並把緩存行狀態置為S;如果不是I,則可以直接讀取緩存中的值,但在此之前必須要等待對其他CPU的監聽結果,如果其他CPU也有該數據的緩存且狀態是M,則需要等待其把緩存更新到內存后再讀取

      • CPU可以將狀態為M/E/S的緩存寫入內存,其中如果緩存行狀態為S,則其他CPU緩存了相同數據的緩存行會無效化

  • 也就是說不會有多個線程同時訪問共享變量,而且共享變量更新是對所有線程可見的,所以原子操作是線程安全的。


免責聲明!

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



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