引入
-
在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緩存了相同數據的緩存行會無效化
-
-
-
也就是說不會有多個線程同時訪問共享變量,而且共享變量更新是對所有線程可見的,所以原子操作是線程安全的。