CAS:compare and swap污秆,也有的叫做 compare and set;意思都差不多墅茉,翻譯過來就是比較并交換或者比較并設(shè)值命黔。
CAS包含三個值,內(nèi)存地址(V)就斤,預(yù)期值(A)悍募,新值(B)。先比較內(nèi)存地址的值和預(yù)期的值是否相等战转,如果相等搜立,就將新值賦在內(nèi)存地址上,否則槐秧,不做任何處理啄踊。這種是樂觀鎖的思想。
源碼解析
CAS操作在JUC中大量用到刁标,在解析AQS那章中颠通,我們也有提到。再回頭看一下AQS中CAS的操作
protected final boolean compareAndSetState(int expect, int update) {
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
這里是對AQS中state變量進行的CAS操作膀懈,要知道顿锰,很多同步類都是通過這個變量來實現(xiàn)線程安全的,所以在AQS中启搂,首先要保證對state的賦值是線程安全的硼控。
在java中,不論什么操作胳赌,只要他還是在Java api級別牢撼,就不能保證他一定是線程安全的,除非這個操作調(diào)用系統(tǒng)資源來支持疑苫。這里保證state線程安全是通過unsafe中的compareAndSwapInt方法來實現(xiàn)的熏版,里面的參數(shù)stateOffset就是內(nèi)存地址(V),expect是預(yù)期值(A)捍掺,新值(B)撼短。在unsafe中,大部分方法都是native的挺勿,而且unsafe可以直接操作系統(tǒng)的內(nèi)存資源曲横,不受jvm限制,通過它來保證線程安全不瓶,應(yīng)該是穩(wěn)了禾嫉,但是unsafe類官方是不建議用的,咱們平時開發(fā)大部分時候也用不著湃番,需要的操作夭织,jdk已經(jīng)幫我們封裝好了。
CAS的問題
CAS雖然看起來很完美吠撮,可以在原子級別保證一個變量的線程安全尊惰,但是它有時候也會出問題,比如著名的ABA問題泥兰。
ABA問題:我們都知道CAS操作是先比較A的預(yù)期值和內(nèi)存地址中的值是否相同弄屡,如果相同就認為此時沒有其他線程修改A值,但是一定是這樣嗎鞋诗?假如一個線程讀取到A值膀捷,此時有另外一個線程將A值改成了B,然后又將B改回了A削彬,這時比較A和預(yù)期值是相同的全庸,就認為A值沒有被改變過秀仲。為了解決ABA的問題,可以使用版本號壶笼,每次修改變量神僵,都在這個變量的版本號上加1,這樣覆劈,剛剛A->B->A保礼,雖然A的值沒變,但是它的版本號已經(jīng)變了责语,再判斷版本號就會發(fā)現(xiàn)此時的A已經(jīng)被別人偷偷改過了炮障。
CAS還有一個性能問題,在大部分使用CAS的時候坤候,都是配合自旋來使用胁赢,這里的自旋,你可以理解為for(;;)這樣的無限循環(huán)铐拐,我們在jdk源碼中找一處來看看
public final int getAndSet(int newValue) {
for (;;) {
int current = get();
if (compareAndSet(current, newValue))
return current;
}
}
這AtomicInteger類中g(shù)etAndSet方法徘键,這是jdk1.7中的代碼,在1.8中換成了另外一種寫法遍蟋,意思都是自旋加CAS吹害,jdk1.7中自旋在AtomicInteger里,而jdk1.8調(diào)用了unsafe中g(shù)etAndSetInt方法虚青,在這個方法中自旋它呀,有興趣的話可以去看看1.8中的源碼。
平時我們開發(fā)過程中棒厘,如果碰到系統(tǒng)蹦了纵穿,大部分人可能第一時間就會想到代碼里是不是出現(xiàn)死循環(huán)了,當(dāng)出現(xiàn)死循環(huán)時奢人,會占用系統(tǒng)大量資源谓媒,造成系統(tǒng)崩潰。在這里我們看到源碼中的自旋就是當(dāng)CAS成功時何乎,才會return句惯。當(dāng)然也不會出現(xiàn)CAS一直失敗的情況,那幾率也太小了支救。因此CAS帶來的性能問題也是需要考慮的抢野。但是從某種意思上來說,這個自旋也是CAS的優(yōu)勢各墨,自旋算是一種非阻塞算法指孤,相對于其他阻塞算法而已,非阻塞是不需要cpu切換時間片保存上下文的贬堵,節(jié)省了大量性能消耗恃轩。
結(jié)論
CAS是一種無鎖解決并發(fā)問題的手段结洼,解決了鎖引起線程切換帶來的性能問題。