CAS是什么?
CAS(Compare And Swap)是一種用于實現(xiàn)無鎖并發(fā)算法的技術(shù)。
大多數(shù)時候它也還有其他叫法:無鎖優(yōu)化、自旋、樂觀鎖
它的核心思想是:通過比較當(dāng)前需要修改的值與預(yù)期原來的值,如果相等,則使用新值進行替換。
這個過程是原子性的,它底層是靠C語言依賴的操作系統(tǒng)的原子操作來保證原子性的,即在這個過程中不會被其他線程打斷。
在Java中,CAS操作主要是通過?java.util.concurrent.atomic
包中的原子類來實現(xiàn)的,如?AtomicInteger
、AtomicLong
等。
原理示例
假設(shè)有一個整數(shù)變量?count
,初始值為0?,F(xiàn)在有兩個線程A和B同時對?count
進行加1操作。使用CAS操作可以確保?count
最終的值為2。
java面試大全:http://kdocs.cn/l/cfoqiIIJ4xmW
線程A讀取?
count
的值,得到0。線程B讀取?
count
的值,得到0。線程A將?
count
的值與預(yù)期值0進行比較,相等,則將?count
的值更新為1。線程B將?
count
的值與預(yù)期值0進行比較,不相等(因為已經(jīng)被線程A更新為1),則重試。線程B重新讀取?
count
的值,得到1。線程B將?
count
的值與預(yù)期值1進行比較,相等,則將?count
的值更新為2。
最終,count
的值為2,實現(xiàn)了無鎖并發(fā)操作。
二、源碼分析
以?java.util.concurrent.atomic.AtomicInteger
為例,這個類提供了一個原子的整數(shù)值,可以用于實現(xiàn)無鎖的整數(shù)操作。我們來看一下它的?compareAndSet
方法的源碼:
public final boolean compareAndSet(int expect, int update){
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
這里的?unsafe
是一個?sun.misc.Unsafe
類型的對象,它提供了底層的CAS操作。valueOffset
是一個靜態(tài)常量,表示?AtomicInteger
內(nèi)部的?value
字段的內(nèi)存偏移量。expect
是預(yù)期值,update
是新值。這個方法會返回一個布爾值,表示操作是否成功。
小結(jié):
atomicXXX類
Compare and Set/Swap 比較并且設(shè)定
cas(V,Expected,NewValue)
if V == EV == New otherwise try again or fail
CPU原語支持 atomic底層調(diào)用到UnSafe這個方法,三個參數(shù)V就是當(dāng)前版本值,Expected期望值,NewValue賦予的新值。
只有當(dāng)V等于E的時候才將新的值賦給這個變量,又因為它是原語支持是CPU級別的,是一個原子操作,所以在設(shè)值時不會有其他線程來插隊設(shè)值。
三、實戰(zhàn)應(yīng)用
我們來看一個實際的例子,如何使用?AtomicInteger
實現(xiàn)一個無鎖的計數(shù)器。
public class Counter {
? ?/*volatile*/ //int count1 = 0;
? ?AtomicInteger count = new AtomicInteger(0);
? ?/*synchronized*/ void m() {
? ? ? ?for (int i = 0; i < 10000; i++)
? ? ? ? ? ?//if count1.get() < 1000
? ? ? ? ? ?count.incrementAndGet(); //count1++
? ?}
? ?public static void main(String[] args) {
? ? ? ?Counter t = new Counter();
? ? ? ?List<Thread> threads = new ArrayList<Thread>();
? ? ? ?for (int i = 0; i < 10; i++) {
? ? ? ? ? ?threads.add(new Thread(t::m, "thread-" + i));
? ? ? ?}
? ? ? ?threads.forEach((o) -> o.start());
? ? ? ?threads.forEach((o) -> {
? ? ? ? ? ?try {
? ? ? ? ? ? ? ?o.join();
? ? ? ? ? ?} catch (InterruptedException e) {
? ? ? ? ? ? ? ?e.printStackTrace();
? ? ? ? ? ?}
? ? ? ?});
? ? ? ?System.out.println(t.count);
? ?}
}
編輯搜圖
在這個例子中,我們創(chuàng)建了一個?Counter
類,內(nèi)部使用?AtomicInteger
實現(xiàn)了無鎖的計數(shù),然后我們創(chuàng)建了10個線程,分別對計數(shù)器進行增加操作。最后我們輸出計數(shù)器的值,可以看到它確實是10000,證明我們的無鎖計數(shù)器是正確的。
四、CAS在日常中的應(yīng)用場景
在實際開發(fā)中,我們可能會遇到以下幾種使用CAS的場景:
無鎖計數(shù)器?:如上面的例子所示,我們可以使用CAS實現(xiàn)一個高效的無鎖計數(shù)器,避免了使用同步鎖帶來的性能開銷。
單例模式?:我們可以使用CAS實現(xiàn)一種線程安全的單例模式,確保在多線程環(huán)境下只創(chuàng)建一個實例。
并發(fā)容器?:在實現(xiàn)高性能的并發(fā)容器時,如?
ConcurrentHashMap
,我們可以使用CAS操作來實現(xiàn)無鎖或低鎖的數(shù)據(jù)結(jié)構(gòu)更新。多線程并發(fā)任務(wù)?:在多線程并發(fā)執(zhí)行任務(wù)時,我們可以使用CAS操作來確保任務(wù)狀態(tài)的正確更新,例如實現(xiàn)一個無鎖的任務(wù)分發(fā)器。
五、ABA問題
如果另一個線程修改V值,假設(shè)原來是A,先修改成B,再修改回成A。
當(dāng)前線程的CAS操作無法分辨當(dāng)前V值是否發(fā)生過變化,這個就是ABA問題。
解決:
在CAS的時候加版本號,每次操作比較下版本號
加 version
A 1.0
B 2.0
A 3.0
cas(version)
原子類 AtomicStampedReference解決ABA問題
ABA問題重要不?如果是基本數(shù)據(jù)類型結(jié)果沒影響,如果是引用對象就不好說了,比如你的女朋友和你復(fù)合,前面經(jīng)過了多少個其他XXX,你覺得有影響不?
面試大全超級詳細?。?/p>
java面試大全:
搜索公眾號回復(fù)eee003獲取
