其實Java並發框架的基石一共有兩塊,一塊是本文介紹的CAS,另一塊就是AQS,后續也會寫博客介紹。
什么是CAS機制
CAS機制是一種數據更新的方式。在具體講什么是CAS機制之前,我們先來聊下在多線程環境下,對共享變量進行數據更新的兩種模式:悲觀鎖模式和樂觀鎖模式。
悲觀鎖更新的方式認為:在更新數據的時候大概率會有其他線程去爭奪共享資源,所以悲觀鎖的做法是:第一個獲取資源的線程會將資源鎖定起來,其他沒爭奪到資源的線程只能進入阻塞隊列,等第一個獲取資源的線程釋放鎖之后,這些線程才能有機會重新爭奪資源。synchronized就是java中悲觀鎖的典型實現,synchronized使用起來非常簡單方便,但是會使沒爭搶到資源的線程進入阻塞狀態,線程在阻塞狀態和Runnable狀態之間切換效率較低(比較慢)。比如你的更新操作其實是非常快的,這種情況下你還用synchronized將其他線程都鎖住了,線程從Blocked狀態切換回Runnable華的時間可能比你的更新操作的時間還要長。
樂觀鎖更新方式認為:在更新數據的時候其他線程爭搶這個共享變量的概率非常小,所以更新數據的時候不會對共享數據加鎖。但是在正式更新數據之前會檢查數據是否被其他線程改變過,如果未被其他線程改變過就將共享變量更新成最新值,如果發現共享變量已經被其他線程更新過了,就重試,直到成功為止。CAS機制就是樂觀鎖的典型實現。
CAS,是Compare and Swap的簡稱,在這個機制中有三個核心的參數:
- 主內存中存放的共享變量的值:V(一般情況下這個V是內存的地址值,通過這個地址可以獲得內存中的值)
- 工作內存中共享變量的副本值,也叫預期值:A
- 需要將共享變量更新到的最新值:B

如上圖中,主存中保存V值,線程中要使用V值要先從主存中讀取V值到線程的工作內存A中,然后計算后變成B值,最后再把B值寫回到內存V值中。多個線程共用V值都是如此操作。CAS的核心是在將B值寫入到V之前要比較A值和V值是否相同,如果不相同證明此時V值已經被其他線程改變,重新將V值賦給A,並重新計算得到B,如果相同,則將B值賦給V。
值得注意的是CAS機制中的這步步驟是原子性的(從指令層面提供的原子操作),所以CAS機制可以解決多線程並發編程對共享變量讀寫的原子性問題。
ABA問題
所謂ABA問題, 就是比較並交換的循環,存在一個時間差,而這個時間差可能帶來意想不到的問題。
比如有兩個線程A、B:
一開始都從主內存中拷貝了原值為3;
A線程執行到var5=this.getIntVolatile,即var5=3。此時A線程掛起;
B修改原值為4,B線程執行完畢;
然后B覺得修改錯了,然后再重新把值修改為3;
A線程被喚醒,執行this.CompareTxchange( )方法,發現這個時候主內存的值等於快照值3,(但是卻不知道B曾經修改過),修改成功。
盡管線程A CAS操作成功,但不代表就沒有問題。有的需求,比如CAS,只注重頭和尾,只要首尾一致就接受。但是有的需求,還看重過程,中間不能發生任何修改。這就引出了原子引用。
原子引用
Int32對整數進行原子操作,如果是一個普通的對象呢?可以用 Interlocked.CompareExchange<T>(T, T, T)泛型來包裝這個普通類,使其操作原子化。
C# 對CAS的ABA問題的解決方案
C#,通過Interlocked方法實現。CAS在.NET中的實現類是Interlocked,內部提供很多原子操作的方法,最終都是調用Interlocked.CompareExchange()
Windows,通過Windows API實現了InterlockedCompareExchangeXYZ系列函數。
CAS機制優缺點
CAS的適用場景
讀多寫少:如果有大量的寫操作,CPU開銷可能會過大,因為沖突失敗后會不斷重試(自旋),這個過程中會消耗CPU
單個變量原子操作:CAS機制所保證的只是一個變量的原子操作。
CAS總結
任何技術都不是完美的,當然,CAS也有他的缺點:
CAS實際上是一種自旋鎖,
一直循環,開銷比較大。
只能保證一個變量的原子操作,多個變量依然要加鎖。
引出了ABA問題(C# Interlocked.CompareExchange()方法 可解決)。
而他的使用場景適合在一些並發量不高、線程競爭較少的情況,加鎖太重。但是一旦線程沖突嚴重的情況下,循環時間太長,為給CPU帶來很大的開銷。
CAS機制的案例:
下面的基本示例展示了無鎖堆棧中的 SpinWait 結構。 如果需要高性能的線程安全堆棧,請考慮使用 System.Collections.Concurrent.ConcurrentStack<T>。
詳解:啟用3個線程給自定義堆棧LockFreeStack<T>的 字段reeStac 添加數據(0-20)。用到cas 技術保證了線程的同步。
LockFreeStack<int> reeStac = new(); for (int i = 1; i <=3; i++) { Thread se = new Thread(test); se.Start(); } void test(){ for (int i = 0; i < 20; i++) { reeStac.Push(i); } } public class LockFreeStack<T> { private volatile Node m_head; private class Node { public Node Next; public T Value; } public void Push(T item) { var spin = new SpinWait(); Node node = new Node { Value = item }, head ; while (true) { head = m_head; node.Next = head; Console.WriteLine("Processor:{0},Thread{1},priority:{2} count:{3} ", Thread.GetCurrentProcessorId(), Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.Priority,item ); Node dd = Interlocked.CompareExchange(ref m_head, node, head);//如果相等 就把node賦值給m_head,返回值都是原來的m_head。 if (dd == head) break;//判斷是否賦值成功。成功就跳出死循環。 spin.SpinOnce(); Console.WriteLine("Processor:{0},Thread{1},priority:{2} spin.SpinOnce()", Thread.GetCurrentProcessorId(), Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.Priority); } } public bool TryPop(out T result) { result = default(T); var spin = new SpinWait(); Node head; while (true) { head = m_head; if (head == null) return false; if (Interlocked.CompareExchange(ref m_head, head.Next, head) == head) { result = head.Value; return true; } spin.SpinOnce(); } } }
