如果懂JMM,這個壓根就不用看。
CAS(Compare And Swap)
其保留有3個值
V(內(nèi)存值)、A(舊的預期值/舊值)、B(要修改的值/新值)。
V指的是主存中的存在的值;A指的是每個線程自己持有的V的副本;B是經(jīng)過一系列自己指定的運算后的結(jié)果。
當且僅當預期值A(chǔ)和內(nèi)存值V相同時,將內(nèi)存值修改為B并返回true,否則什么都不做并返回false
設(shè)定:
1.內(nèi)存中有個空間(Q)存儲了一個值為1,我們使用多線程且使用CAS進行修改會發(fā)生如下反應。
2.有2個線程(A和B)同時對Q進行 +1 操作。(可以參考JDK 1.7中AtomicInteger)
// JDK 1.7 中的代碼示例
private volatile int value;
private static final Unsafe unsafe = Unsafe.getUnsafe();
public final int getAndIncrement() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return current;
}
}
public final int get() {
return value;
}
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
根據(jù)JMM可以得知,2個線程會各自獲取一份Q的副本(值為1)。
1.線程A開始運行,current獲取到線程中的副本值為1,然后執(zhí)行完成+1操作,此時線程切換給B操作。
2.線程B開始運行,current獲取到線程中的副本值為1,然后執(zhí)行完成+1操作,`之后拿著線程B中Q的副本[current]與Q的值進行比較,如果兩值相等就將計算出來的next進行替換掉Q的原值(CAS操作)`,此時線程切換給A操作。
3.線程A開始運行,拿著線程A中Q的副本進行相同的比較與賦值操作(CAS操作),在V與A的比較中發(fā)現(xiàn)不對等(**內(nèi)存中的Q值為2,線程B的副本值為1**),于是認為value正在被另外一個線程操作,所以不能進行值替換。**
4.這個時候線程A會再一次取Q中的值作為新的副本,再次進行+1操作并通過CAS嘗試寫入新值,如果一直寫不進去這個過程會不斷重復,直到能成功的執(zhí)行為止。
根據(jù)代碼可以看出這個
value的volatile修飾符十分重要,當操作失敗后能及時的獲取到其他線程對其的修改值。
如果去掉volatile修飾符,那不同線程之間就完全感知不到對方對值的修改,也就是說在第4步的時候,線程A永遠取不到Q的新值。
至于compareAndSet方法,我們也能從示例代碼中清楚的看到是調(diào)用了Unsafe這個類中的方法。
Unsafe提供了硬件級別的原子操作,但不建議自己使用。
CAS的ABA問題
在上訴基礎(chǔ)上,多添加幾個線程進行對Q的操作,要求是加減都有。
那線程中CAS操作會不會存在問題?
線程L的副本存儲的為1,我們不能確保在線程L中進行V和A的比較操作之前,是否有其他線程將Q的值改為2后又改回了1。
這就是ABA問題,當然可以采用版本號等方式解決。
ABA問題的例子:
主存中的V的值為1。
線程A中副本值為1,然后進行+1操作,線程切換。
線程B中的副本之為1,然后進行+1操作,最后通過CAS對V的值進行寫入,現(xiàn)在V的值為2,線程切換。
線程C中的副本之為2,然后進行-1操作,最后通過CAS對V的值進行寫入,現(xiàn)在V的值為1,線程切換。
線程A之前已經(jīng)計算完了,現(xiàn)在嘗試進行用CAS操作,發(fā)現(xiàn)V和自己的副本值相同后進行寫入操作。
雖然看起來沒什么問題,但可能在某些邏輯中就會導致潛在問題的出現(xiàn)。
額外補充
觀看其他的java.util.concurrent中的代碼不難發(fā)現(xiàn),該包是基于CAS的構(gòu)建的,而CAS又依賴于Unsafe類。