1. 程式人生 > >簡析CAS機制與實現原理

簡析CAS機制與實現原理

在學習CAS的過程中,我百思不得其解的一個問題就是在多cpu併發的環境下,CAS如何保證執行緒的安全性呢?關於這個問題下面的兩篇部落格寫的比較不錯,基本把其中的原理解釋清楚了,這裡我只作一個簡單的闡述。

一.鎖機制的缺點

在jdk5以前,java基本只有靠synchronized關鍵字來保證同步,這會導致需要同步的物件被加上獨佔鎖,也就是我們常說的悲觀鎖。悲觀鎖存在一些問題,典型的如:

1.在多執行緒競爭下,加鎖和釋放鎖會導致比較多的上下文切換和排程延時,引起效能問題。

2.一個執行緒持有鎖會導致其他所有需要此鎖的執行緒掛起

      與悲觀鎖對應的是樂觀鎖,樂觀鎖在處理某些場景的時候有更好的表現,所謂樂觀鎖就是認為在併發場景下大部分時候都不會產生衝突,因此每次讀寫資料讀不加鎖而是假設沒有衝突去完成某項操作,如果因為衝突失敗就重試,直到成功為止。樂觀鎖用到的機制就是CAS。

二.什麼是CAS

CAS操作包含三個運算元——記憶體位置(V),預期原值(A)和新值(B)。如果記憶體位置的值與預期原值相匹配,那麼處理器將會自動將該位置值更新為新值,否則,不做任何操作。無論哪種情況,它都會在CAS指令之前返回該位置的值。

通過以上定義我們知道CAS其實是有三個步驟的

1.讀取記憶體中的值

2.將讀取的值和預期的值比較

3.如果比較的結果符合預期,則寫入新值

        現在的CPU都支援“讀-比較-修改”原子操作,也就是一個cpu在執行這個操作的時候,是絕對不會被其他執行緒中斷的,但是多cpu環境就不一定了。可能在cpu1執行完比較操作準備修改的時候,另一塊

cpu2火速完成了一次“讀-比較-修改”操作從而讓記憶體中的值發生變化。而此時cpu1再寫入明顯就不對了。並且這個場景也並沒有違背該命令的原子性。

        解決這個問題的答案其實也很簡單,那就是就是volatile我之前的一篇部落格討論了volitile變數的寫入機制,volatile的官方解釋是可以保證可見性、順序性、一致性。下面簡單解讀一下這三個特性:

可見性:volatile修飾的物件在載入時會告知JVM,物件在cpu的快取上對多個執行緒是同時可見的。

順序性:這裡有JVM記憶體屏障的概念,可以簡單理解為:可以保證執行緒操作物件時是順序執行的不會進行指令重排序

一致性:可以保證多個執行緒讀取資料時,讀到的資料是最新的。

        在上面的場景中,解決問題的關鍵就是volatile的一致性,volitile的寫操作是安全的,因為他在寫入的時候lock聲言會鎖住cpu匯流排導致其他CPU不能訪問記憶體(現在多用快取一致性協議,處理器嗅探總線上傳播的資料來判斷自己快取的值是否過期),所以,寫入的時候若其他cpu修改了記憶體值,那麼寫入會失敗。上面的問題中,由於cpu1CAS指令執行一半的時候cpu2火速修改了變數的值,因此這就讓該變數在所有cpu上的快取失效,cpu1在進行寫入操作的時候,也會發現自己的快取失效,那麼CAS操作就會失敗(在javaautomicinteger中,會不停的CAS直到成功)。所以即使是在多cpu多執行緒環境下,CAS機制依然能夠保證執行緒的安全。

        但是CAS也並非完美的,CAS存在3個問題,ABA問題,迴圈時間長的話開銷大(也就是說多衝突環境下樂觀鎖的重試消耗大),以及只能保證一個共享變數的原子操作,本文就不再詳細討論了。