CAS算法簡介

在瞭解CAS之前,我們先看看幾個概念來知道爲什麼會有CAS的出現

樂觀鎖和悲觀鎖

悲觀鎖:獨佔鎖是一種悲觀鎖,synchronized就是一種獨佔鎖,它假設最壞的情況,並且只有在確保其它線程不會造成干擾的情況下執行,會導致其它所有需要鎖的線程掛起,等待持有鎖的線程釋放鎖。

樂觀鎖:每次不加鎖而是假設沒有衝突而去完成某項操作,如果因爲衝突失敗就重試,直到成功爲止。

 

悲觀鎖的代價

鎖是用來做併發最簡單的方式,當然其代價也是最高的。內核態的鎖的時候需要操作系統進行一次上下文切換,加鎖、釋放鎖會導致比較多的上下文切換和調度延時,等待鎖的線程會被掛起直至鎖釋放。在上下文切換的時候,cpu之前緩存的指令和數據都將失效,對性能有很大的損失。用戶態的鎖雖然避免了這些問題,但是其實它們只是在沒有真實的競爭時纔有效。

Java在JDK1.5之前都是靠synchronized關鍵字保證同步的,在掛起和恢復執行過程中存在着很大的開銷。鎖還存在着其它一些缺點,當一個線程正在等待鎖時,它不能做任何事。如果一個線程在持有鎖的情況下被延遲執行,那麼所有需要這個鎖的線程都無法執行下去。如果被阻塞的線程優先級高,而持有鎖的線程優先級低,將會導致優先級反轉(Priority Inversion)。

 

有些人此時想到volatile,但是我們來看看volatile

volatile

與鎖相比,volatile變量是一和更輕量級的同步機制,因爲在使用這些變量時不會發生上下文切換和線程調度等操作,但是volatile變量也存在一些侷限:不能用於構建原子的複合操作,因此當一個變量依賴舊值時就不能使用volatile變量。所以volatile其實只是用於變量共享

 

此時,出現了CAS算法!

 

什麼是CAS算法

CAS是英文單詞CompareAndSwap的縮寫,中文意思是:比較並替換。CAS是項樂觀鎖技術,當多個線程嘗試使用CAS同時更新同一個變量時,只有其中一個線程能更新變量的值,而其它線程都失敗,失敗的線程並不會被掛起,而是被告知這次競爭中失敗,並可以再次嘗試。

 

CAS算法理解

CAS包含三個參數 CAS(V,E,N)。V表示要更新的變量,E表示預期的值,N表示新值。僅當V值等於E值時,纔會將V的值設置成N,否則什麼都不做。最後CAS返回當前V的值。CAS算法需要你額外給出一個期望值,也就是你認爲現在變量應該是什麼樣子,如果變量不是你想象的那樣,那說明已經被別人修改過。你就重新讀取,再次嘗試修改即可。

 

CAS優點

可以避免優先級倒置和死鎖等危險,競爭比較便宜,協調發生在更細的粒度級別,允許更高程度的並行機制等等

 

CAS缺點

CAS雖然很高效的解決了原子操作問題,但是CAS仍然存在三大問題。

  1.     循環時間長開銷很大。
  2.     只能保證一個共享變量的原子操作。
  3.     ABA問題。

循環時間長開銷很大:
如果CAS失敗,會一直進行嘗試。如果CAS長時間一直不成功,可能會給CPU帶來很大的開銷。

只能保證一個共享變量的原子操作:
當對一個共享變量執行操作時,我們可以使用循環CAS的方式來保證原子操作,但是對多個共享變量操作時,循環CAS就無法保證操作的原子性,這個時候就可以用鎖來保證原子性。

什麼是ABA問題?

因爲CAS需要在操作值的時候檢查下值有沒有發生變化,如果沒有發生變化則更新,但是如果一個值原來是A,變成了B,又變成了A,那麼使用CAS進行檢查時會發現它的值沒有發生變化,但是實際上卻變化了。

ABA問題怎麼解決?

ABA問題的解決思路就是使用版本號。在變量前面追加上版本號,每次變量更新的時候把版本號加一,那麼A-B-A 就會變成1A-2B-3A。

從Java1.5開始JDK的atomic包裏提供了一個類AtomicStampedReference來解決ABA問題。這個類在內部不僅維護了對象值,還維護了一個時間戳(可以是任意的一個整數來表示狀態值)。當設置對象值時,對象值和狀態值都必須滿足期望值纔會寫入成功。因此即使對象被反覆讀寫,寫會原值,只要狀態值發生變化,就能防止不恰當的寫入。 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章