CAS簡介
CAS(Compare And Swap),比較並交換,是CPU硬件級別提供的功能,好比IA64,X86指令集中用來完成CAS功能的指令集是cmpxchg。java.util.concurrent包中使用該技術實現樂觀鎖,換句話說java.util.concurrent包是徹底創建在CAS之上,AQS同步組件、Atomic原子類操做等都是基於CAS實現的。CAS在JUC包中所處的位置如圖:
java
CAS有3個操做數,內存值V,舊的預期值A,要修改的新值B。當且僅當預期值A和內存值V相同時,將內存值V修改成B,不然什麼都不作。併發
Java中的CAS
java中具體的CAS操做是由類sun.misc.Unsafe來負責的。Unsafe類提供了硬件級別的原子操做(即native方法),Java使用native方法來間接訪問操做系統底層(如系統硬件等),擴展Java程序的功能。native具體方法使用C++實現。sun.misc.Unsafe提供了三個CAS操做,從方法名便可看出,分別針對Object類型、int類型和long類型。上文說過CAS有3個操做數,內存值V,舊的預期值A,要修改的新值B,可是仔細看Unsafe提供的CAS操做有4個操做數,這是由於Unsafe提供的CAS操做具體實現是使用C++來實現的,C++能夠直接操做內存,效提高操做效率,直接用對象和字段偏移量來獲取內存值V,第一個參數是要修改field所在的對象 ,第二個參數是對象中須要修改的field的偏移量(field偏移量這個參數所表明的意思是某個字段相對Java對象的「起始地址」的偏移量,Unsafe提供了一個方法objectFieldOffset(Field var1)來獲取這個參數值,對於field偏移量的理解能夠參考這裏),第三個參數就是舊的預期值A,第四個參數是要修改的新值B
Unsafe提供了三個基本CAS操做
操作系統
CAS缺陷
1)循環時間長致使CPU負載高
CAS操做通常和循環搭配使用(即咱們所說的自旋CAS),直到修改爲功纔會退出循環。若是在某些併發量較大的狀況下,變量的值始終被別的線程修改,自旋CAS長時間地不成功,則會給CPU帶來很是大的開銷。CAS 在併發量不是很高的狀況下效率遠遠高於鎖機制。還有一種策略就是限制CAS自旋的次數,在JUC中有些地方就使用了這種策略,例如阻塞隊列中的SynchronousQueue。線程
2)只能保證一個共享變量原子操做
CAS只能針對一個共享變量,若是是多個共享變量有三種方案,一是使用鎖;二是把多個變量合併爲一個變量,好比juc包中的Phaser類中的status字段能夠表示四個變量;三是把多個變量放在一個對象裏來使用AtomicReference類進行CAS操做改變引用地址以達到修改變量的目的。對象
3)ABA問題
CAS須要檢查操做值有沒有發生改變,若是沒有發生改變則更新。可是存在這樣一種狀況:若是一個值原來是A,變成了B,而後又變成了A,那麼在CAS檢查的時候會發現沒有改變,可是實質上它已經發生了改變,這就是所謂的ABA問題。對於ABA問題其解決方案是加上版本號,即在每一個變量都加上一個版本號,每次改變時加1,即A —> B —> A,變成A1 —> B2 —> A3。JUC包中提供了一個AtomicStampedReference類來解決ABA問題。 AtomicStampedReference 本質是有一個int 值做爲版本號,每次更改前先取到這個int值的版本號,等到修改的時候,比較當前版本號與當前線程持有的版本號是否一致,若是一致,則進行修改,並將版本號加1,在zookeeper中保持數據的一致性也是用的這種方式;此外,JUC包中還有一個AtomicMarkableReference類,這個類則是將一個boolean值做是否有更改的標記,本質就是它的版本號只有兩個,true和false,修改的時候在這兩個版本號之間來回切換,這樣作並不能解決ABA的問題。blog