什麼是CAS/ABA以及volatile的使用

1.CAS原理介紹

CAS全稱 比較和交換(Conmpare And Swap),它是一種思想,是樂觀鎖。

內存地址V,內存地址V中舊值C,獲取的舊預期值A,要修改的新值B, 工作內存W

我們需要把C修改成B時

首先從V中取到值進入W中時,不像悲觀鎖一樣,把V中值鎖住,而是先把A放入W中,

然後用A和C進行比較,如果不一樣,則修改失敗。

然後重新從V中獲取A,這個過程叫自旋,使用了自旋鎖。

再次進行比較,如果此次獲取的A和C一樣,則修改成功,最後進行SWAP更新C。

2.ABA介紹

因爲CAS需要在操作值的時候檢查下值有沒有發生變化,

如果沒有發生變化則更新,

但是如果一個值原來是A,變成了B,又變成了A,那麼使用CAS進行檢查時會發現它的值沒有發生變化,

但是實際上卻變化了。

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

如果A==A && 1!=3 時 修改失敗

如果A==A && 1==1時 修改成功

如果A!=B && 1!=2 時  自旋

 

3.CAS缺點

  cpu開銷大,因爲如果自旋長時間不成功,給CPU帶來很大壓力

 不能保證代碼塊原子性: 即一個操作或者多個操作 要麼全部執行並且執行的過程不會被任何因素打斷,要麼就都不執行。因爲沒有鎖機制,當有三個變量一起操作時,如果有一個修改失敗了,就會影響最後的結果,就不得不使用鎖機制了。

 可見性問題:當多個線程一起併發操作時,同時更新修改,可能存在可見性不及時現象。

指令重排序問題: 一般來說,處理器爲了提高程序運行效率,可能會對輸入代碼進行優化,

它不保證程序中各個語句的執行先後順序同代碼中的順序一致,

但是它會保證程序最終執行結果和代碼順序執行的結果是一致的。(基本判斷方法是檢查上下代碼是否有數據依賴)

例如:單例模式中,併發高的時候指令重排序可能會導致創建空對象情況。

4.volatile的使用

volatile關鍵字使用了CAS原理,並進行了優化。

一旦一個共享變量(類的成員變量、類的靜態成員變量)被volatile修飾之後,那麼就具備了兩層語義:

  1)保證了不同線程對這個變量進行操作時的可見性,即一個線程修改了某個變量的值,這新值對其他線程來說是立即可見的。(它會強制將對緩存的修改操作立即寫入主存;如果是寫操作,它會導致其他CPU中對應的緩存行無效)

  2)禁止進行指令重排序。(它確保指令重排序時不會把其後面的指令排到內存屏障之前的位置,也不會把前面的指令排到內存屏障的後面;即在執行到內存屏障這句指令時,在它前面的操作已經全部完成)

缺點 :對任意單個volatile變量的讀/寫具有原子性,但類似於volatile++這種複合操作不具有原子性。(當取到值的時候,再進行計算後,還沒有及時更新,其他線程可能已經多次修改這個值了)

優點:效率高於synchronized。

使用volatile必須具備以下2個條件:

  1)對變量的寫操作不依賴於當前值

  2)該變量沒有包含在具有其他變量的不變式中

使用實例:

1 public class Singleton {
2	
3	private volatile static Singleton instance = null; //
4
5	private Singleton() {
6	};
7
8	public static Singleton getInstance() {
9
10		if (instance == null) {
11
12			synchronized (Singleton.class) {
13				if (instance == null) {
14					instance = new Singleton();
15				}
16			}
17		}
18		return instance;
19	}
20
21}

  使用  volatile 原因:

  1)  第14行代碼new出來的對象,別的線程立馬可見,避免進入第10行代碼。

  2)  對於第14行 instance = new Singleton(); 

          可以分解爲3個步驟:
         1 memory=allocate();// 分配內存 相當於c的malloc
         2 ctorInstanc(memory) //初始化對象
         3 instance =memory //設置instance 指向剛分配的地址

           上面的代碼在編譯器運行時,2 3步半初始化過程,可能會出現重排序 從1-2-3 排序爲1-3-2

           如此在多線程下就會出現問題

           例如現在有2個線程A,B

            線程A在執行第5行代碼時,B線程進來,而此時A執行了 1和3,沒有執行2,此時B線程判斷s不爲null 直接返回一個未初             始化的對象,就會出現問題.

 加上volatile就是爲了防止產生指令的重排序問題.

 

 

 

 

 

 

 

 

 

 

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