CAS無鎖模式
一、java記憶體模型:JMM
在記憶體模型當中定義一個主記憶體,所有宣告的例項變數都存在於主記憶體當中,主記憶體的資料會共享給所有執行緒,每一個執行緒有一個塊工作記憶體,工作記憶體當中主記憶體資料的副本當更新資料時,會將工作記憶體中的資料同步到主記憶體當中;
二、什麼是CAS
CAS:Compare and Swap,即比較再交換。
jdk5增加了併發包java.util.concurrent.*,其下面的類使用CAS演算法實現了區別於synchronouse同步鎖的一種樂觀鎖。JDK 5之前Java語言是靠synchronized關鍵字保證同步的,這是一種獨佔鎖,也是是悲觀鎖。
三、CAS演算法理解
(1)與鎖相比,使用比較交換(下文簡稱CAS)會使程式看起來更加複雜一些。但由於其非阻塞性,它對死鎖問題天生免疫,並且,執行緒間的相互影響也遠遠比基於鎖的方式要小。更為重要的是,使用無鎖的方式完全沒有鎖競爭帶來的系統開銷,也沒有執行緒間頻繁排程帶來的開銷,因此,它要比基於鎖的方式擁有更優越的效能。
(2)無鎖的好處:
第一,在高併發的情況下,它比有鎖的程式擁有更好的效能;
第二,它天生就是死鎖免疫的。
(3)CAS演算法的過程是這樣:
它包含三個引數CAS(V,E,N):V表示要更新的變數,E表示預期值,N表示新值。僅當V值等於E值時,才會將V的值設為N,如果V值和E值不同,則說明已經有其他執行緒做了更新,則當前執行緒什麼都不做。最後,CAS返回當前V的真實值。
執行過程:
1.執行緒訪問時,先會將主記憶體中的資料同步到執行緒的工作記憶體當中
2.假設執行緒A和執行緒B都有對資料進行更改,那麼假如執行緒A先獲取到執行許可權
3.執行緒A先會對比工作記憶體當中的資料和主記憶體當中的資料是否一致,如果一致(V==E)則進行更新,不一致則重新整理資料,重新迴圈判斷
4.這時更新完畢後,執行緒B也要進行資料更新,主記憶體資料和工作記憶體資料做對比,如果一致則進行更新,不一致則將主記憶體資料重新更新到工作記憶體,然後迴圈再次對比兩個記憶體中的資料直到一致為止
(4)CAS操作是抱著樂觀的態度進行的,它總是認為自己可以成功完成操作。當多個執行緒同時使用CAS操作一個變數時,只有一個會勝出,併成功更新,其餘均會失敗。失敗的執行緒不會被掛起,僅是被告知失敗,並且允許再次嘗試,當然也允許失敗的執行緒放棄操作。基於這樣的原理,CAS操作即使沒有鎖,也可以發現其他執行緒對當前執行緒的干擾,並進行恰當的處理。
(5)簡單地說,CAS需要你額外給出一個期望值,也就是你認為這個變數現在應該是什麼樣子的。如果變數不是你想象的那樣,那說明它已經被別人修改過了。你就重新讀取,再次嘗試修改就好了。
(6)在硬體層面,大部分的現代處理器都已經支援原子化的CAS指令。在JDK 5.0以後,虛擬機器便可以使用這個指令來實現併發操作和併發資料結構,並且,這種操作在虛擬機器中可以說是無處不在。
四、CAS缺點
CAS存在一個很明顯的問題,即ABA問題;
問題:如果變數V初次讀取的時候是A,並且在準備賦值的時候檢查到它任然是A,那能說明它的值沒有被其他執行緒修改過了嗎?
如果在這段時間曾經被改成B,然後有改回A,那CAS操作就會誤任務它從來沒有被修改過。正對這種情況,java併發包提供了一個帶有標記的原子應用類AtomicStampedRefernce,它可以通過變數值的版本來保證CAS的正確性;
五、保證執行緒安全的三個方面
1.原子性:保證同一時刻該資源只能有一個執行緒訪問修改,其他執行緒阻塞等待,例如Atomic包,鎖
原子性: 互斥訪問,Atomic包,CAS演算法,Synchronized,Lock
2.可見性:一個執行緒對於主記憶體的資料操作對於其他執行緒是可見的
可見性:synchronized,volatile
3.有序性:一個執行緒觀察其他執行緒中指令執行順序,由於指令重排序存在,觀察結果一般雜亂無序
順序性:happends-be