成交型網(wǎng)站制作河北seo基礎(chǔ)教程
CAS是什么
CAS是compare and swap的縮寫,即我們所說的比較交換。該操作的作用就是保證數(shù)據(jù)一致性、操作原子性。
cas是一種基于鎖的操作,而且是樂觀鎖
。在java中鎖分為樂觀鎖和悲觀鎖。悲觀鎖是將資源鎖住,等之前獲得鎖的線程釋放鎖之后,下一個線程才可以訪問。而樂觀鎖
采取了一種寬泛的態(tài)度,通過某種方式不加鎖來處理資源,比如通過給記錄加version
來獲取數(shù)據(jù),性能較悲觀鎖有很大的提高。
CAS 操作包含三個操作數(shù) —— 內(nèi)存位置(V)、預(yù)期原值(A)和新值(B)。如果內(nèi)存地址里面的值和A的值是一樣的,那么就將內(nèi)存里面的值更新成B。否則將不做任何處理。
CAS是通過無限循環(huán)來獲取數(shù)據(jù)的,若果在第一輪循環(huán)中,a線程獲取地址里面的值被b線程修改了,那么a線程需要自旋,到下次循環(huán)才有可能機會執(zhí)行。
為什么說CAS能很好的保證數(shù)據(jù)一致性,因為它是直接從硬件層面保證了原子性。
CAS是一條CPU的原子指令(cmpxchg指令),Unsafe提供的CAS方法(如compareAndSwapXXX)底層實現(xiàn)即為CPU指令cmpxchg。
執(zhí)行cmpxchg指令的時候,會判斷當前系統(tǒng)是否為多核系統(tǒng),如果是就給總線加鎖,只有一個線程會對總線加鎖成功,加鎖成功之后會執(zhí)行cas操作,也就是說CAS的原子性實際上是CPU實現(xiàn)獨占的。比起用synchronized重量級鎖, 這里的排他時間要短很多, 所以在多線程情況下性能會比較好。
測試案例講解
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;public class TestCas {public static int count = 0;private final static int MAX_TREAD=10;public static AtomicInteger atomicInteger = new AtomicInteger(0);public static void main(String[] args) throws InterruptedException {/*CountDownLatch能夠使一個線程在等待另外一些線程完成各自工作之后,再繼續(xù)執(zhí)行。使用一個計數(shù)器進行實現(xiàn)。計數(shù)器初始值為線程的數(shù)量。當每一個線程完成自己任務(wù)后,計數(shù)器的值就會減一。當計數(shù)器的值為0時,表示所有的線程都已經(jīng)完成一些任務(wù),然后在CountDownLatch上等待的線程就可以恢復(fù)執(zhí)行接下來的任務(wù)。*/CountDownLatch latch = new CountDownLatch(MAX_TREAD);//匿名內(nèi)部類Runnable runnable = new Runnable() {@Overridepublic void run() {for (int i = 0; i < 1000; i++) {count++;atomicInteger.getAndIncrement();}latch.countDown(); // 當前線程調(diào)用此方法,則計數(shù)減一}};//同時啟動多個線程for (int i = 0; i < MAX_TREAD; i++) {new Thread(runnable).start();}latch.await(); // 阻塞當前線程,直到計數(shù)器的值為0System.out.println("理論結(jié)果:" + 1000 * MAX_TREAD);System.out.println("static count: " + count);System.out.println("AtomicInteger: " + atomicInteger.intValue());}
}
我們發(fā)現(xiàn)每次運行,atomicInteger 的結(jié)果值都是正確的,count++的結(jié)果卻不對
為什么AtomicInteger
類的操作能保證數(shù)據(jù)一致性呢個?進入它的源碼:
public final int getAndIncrement() {return unsafe.getAndAddInt(this, valueOffset, 1);
}public final int getAndAddInt(Object var1, long var2, int var4) {int var5;do {var5 = this.getIntVolatile(var1, var2);} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));return var5;
}
//cas方法
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
可以看到最終是調(diào)用了Unsafe類中的compareAndSwapInt
,該方法就是CAS方法其中的一個。Unsafe類中總共有三個涉及CAS的方法
/*
@param o 包含要修改的字段的對象
@param offset 字段在對象內(nèi)的偏移量
@param expected 期望值(舊的值)
@param update 更新值(新的值)
@return true 更新成功 | false 更新失敗
*/
public final native boolean compareAndSwapObject(Object o, long offset, Object expected, Object update);
public final native boolean compareAndSwapInt( Object o, long offset, int expected,int update);
public final native boolean compareAndSwapLong( Object o, long offset, long expected, long update);
在執(zhí)行 Unsafe 的 CAS 方法的時候,這些方法首先將內(nèi)存位置的值與預(yù)期值(舊的值)比較,如果相匹配,那么處理器會自動將該內(nèi)存位置的值更新為新值,并返回 true ;如果不相匹配,處理器不做任何操作,并返回 false 。
總的來說,AtomicInteger
類主要利用CAS (compare and swap) + volatile和 native
方法來保證原子操作。
通過看上述getAndAddInt
方法源碼,可見如果cas失敗會不斷循環(huán)重復(fù)嘗試。這就常說的cas自旋
,所謂自旋其實是重復(fù)調(diào)用compareAndSwap
方法,涉及到的方法有以下幾個,也是在Unsafe
類中。
- getAndAddInt
- getAndAddLong
- getAndSetInt
- getAndSetLong
- getAndSetObject
CAS缺點
-
ABA問題。因為CAS需要在操作值的時候檢查下值有沒有發(fā)生變化,如果沒有發(fā)生變化則更新,但是如果一個值原來是A,變成了B,又變成了A,那么使用CAS進行檢查時會發(fā)現(xiàn)它的值沒有發(fā)生變化,但是實際上卻變化了。
-
循環(huán)時間長開銷大。高并發(fā)下N多線程同時去操作一個變量,會造成大量線程CAS失敗,然后處于自旋狀態(tài),導(dǎo)致嚴重浪費CPU資源,降低了并發(fā)性。
ABA問題解決方案(AtomicStampedReference)
JDK 的提供了一個類似 AtomicStampedReference
類來解決 ABA 問題。
AtomicStampReference 在 CAS 的基礎(chǔ)上增加了一個 Stamp 整型 印戳(或標記),使用這個印戳可以來覺察數(shù)據(jù)是否發(fā)生變化,給數(shù)據(jù)帶上了一種實效性的檢驗。
AtomicStampReference 的 compareAndSet
方法首先檢查當前的對象引用值是否等于預(yù)期引用,并且當前印戳( Stamp )標志是否等于預(yù)期標志,如果全部相等,則以原子方式將引用值和印戳( Stamp )標志的值更新為給定的更新值。
-
AtomicStampReference 的構(gòu)造器
/** * @param initialRef初始引用 * @param initialStamp初始戳記 */ AtomicStampedReference(V initialRef, int initialStamp)
-
AtomicStampReference的核心方法
AtomicStampReference
類中有自己的compareAndSet
方法,進入源碼:/** * expectedReference 引用的舊值 * newReference 引用的新值 * expectedStamp 舊的戳記 * newStamp 新的戳記 */ public boolean compareAndSet(V expectedReference,V newReference,int expectedStamp,int newStamp) {Pair<V> current = pair;returnexpectedReference == current.reference &&expectedStamp == current.stamp &&((newReference == current.reference &&newStamp == current.stamp) ||casPair(current, Pair.of(newReference, newStamp))); //unsafe類中的cas }
可以看到舊值
expectedReference
和舊印戳expectedStamp
都會進行比較,都滿足才會調(diào)用Unsafe中的cas方法。