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就是爲了防止產生指令的重排序問題.