CAS操作与ABA问题
我們在使用鎖時,線程獲取鎖是一種悲觀鎖策略,即假設(shè)每一次執(zhí)行臨界區(qū)代碼都會產(chǎn)生沖突, 所以當(dāng)前線程獲取到鎖的時候同時也會阻塞其他線程獲取該鎖。而CAS操作(又稱為無鎖操作)是一種樂觀鎖策略,它假設(shè)所有線程訪問共享資源的時候不會出現(xiàn)沖突,既然不會出現(xiàn)沖突自然而然就不會阻塞其他線程的操作。因此,線程就不會出現(xiàn)阻塞停頓的狀態(tài)。那么,如果出現(xiàn)沖突了怎么辦?無鎖操作是使用CAS(compare and swap)又叫做比較交換來鑒別線程是否出現(xiàn)沖突,出現(xiàn)沖突就重試當(dāng)前操作直到?jīng)]有沖突為止。
CAS的操作過程
CAS即Compare and Swap,CAS比較交換的過程可以通俗的理解為CAS(V、A、B),包含三個值分別為: V內(nèi)存中實際值、 A預(yù)期原值(舊值) 、B更新的新值。當(dāng)V和A相同時,也就是說舊值和內(nèi)存中實際的值相同表明該值沒有被其他線程更改過,即該舊值A(chǔ)就是目前來說最新的值了,自然而然可以將新值B賦值給V。反之,V和A不相同,表明該值已經(jīng)被其他線程改過了則該舊值A(chǔ)不是最新版本的值了,所以不能將新值B賦給V,返回V即可。當(dāng)多個線程使用CAS操作一個變量時,只有一個線程會成功,并成功更新,其余會失敗。失敗的線程會重新嘗試,當(dāng)然也可以選擇掛起線程。
CAS的實現(xiàn)需要硬件指令集的支撐,在JDK1.5后虛擬機(jī)才可以使用處理器提供的CMPXCHG指令實現(xiàn)。CAS支持原子更新操作,適用于計數(shù)器,序列發(fā)生器等場景(序列發(fā)生器就是用來給變量自增的工具),CAS屬于樂觀鎖機(jī)制,號稱lock-free(其實只是上層感知無鎖,底層還是有加鎖操作的)。CAS操作失敗時由開發(fā)者決定是繼續(xù)嘗試還是執(zhí)行別的操作。
基于CAS實現(xiàn)的工具
java.util.concurrent包都中的實現(xiàn)類都是基于volatile和CAS來實現(xiàn)的。尤其java.util.concurrent.atomic包下的原子類。 就拿AtomicInteger來說:
可以看到 AtomicInteger 底層用的是volatile的變量和CAS來進(jìn)行更改數(shù)據(jù)的。volatile保證可見性,多線程并發(fā)時,一個線程修改數(shù)據(jù),可以保證其它線程立馬看到修改后的值,CAS則保證數(shù)據(jù)更新的原子性。
CAS多數(shù)情況下對開發(fā)者來說是透明的。J.U.C的atomic包提供了常用的原子性數(shù)據(jù)類型以及引用、數(shù)組等相關(guān)原子類型和更新操作工具,是很多線程安全程序的首選。Unsafe類雖提供CAS服務(wù),但因能夠操縱任意內(nèi)存地址讀寫而有隱患。JDK9以后,可以使用Variable Handle API來替代Unsafe
模擬實現(xiàn)CAS
CAS需要硬件層面的支持,所以模擬還是用synchronized來實現(xiàn)一下:
public class TestImplementCAS {public static void main(String[] args) {
final CompareAndSwap cas = new CompareAndSwap();
for (int i = 0; i < 10; i++) {
new Thread(()->{
int expectedValue = cas.get();
boolean b = cas.compareAndSet(expectedValue, (int)(Math.random() * 101));
System.out.println(b);
}).start();
}
}
}
class CompareAndSwap{
private int value;
//獲取內(nèi)存值
public synchronized int get(){
return value;
}
//比較
public synchronized int compareAndSwap(int expectedValue, int newValue){
int oldValue = value;
if(oldValue == expectedValue){
this.value = newValue;
}
return oldValue;
}
//設(shè)置
public synchronized boolean compareAndSet(int expectedValue, int newValue){
return expectedValue == compareAndSwap(expectedValue, newValue);
}
}
CAS的缺點
1、ABA問題
因為CAS會檢查舊值有沒有變化,這里存在這樣一個有意思的問題。比如一個舊值A(chǔ)變?yōu)榱顺葿,然后再變成A,剛好在做CAS時檢查發(fā)現(xiàn)舊值并沒有變化依然為A,但是實際上的確發(fā)生了變化。解決方案可以沿襲數(shù)據(jù)庫中常用的樂觀鎖方式,添加一個版本號可以解決。在JDK1.5后的atomic包中提供 了AtomicStampedReference來解決ABA問題,解決思路就是這樣的。如果需要解決ABA問題,互斥與同步可能比CAS更高效。
2、自旋會浪費(fèi)大量的處理器資源
與線程阻塞相比,自旋會浪費(fèi)大量的處理器資源。這是因為當(dāng)前線程仍處于運(yùn)行狀況,只不過跑的是無用指令。它
期望在運(yùn)行無用指令的過程中,鎖能夠被釋放出來。JVM給出的方案是自適應(yīng)自旋,根據(jù)以往自旋等待時能否獲取鎖,來動態(tài)調(diào)整自旋的時間。
3、公平性問題
自旋狀態(tài)還帶來另外一個副作用,不公平的鎖機(jī)制。處于阻塞狀態(tài)的線程,無法立刻競爭被釋放的鎖。然而,處于
自旋狀態(tài)的線程,則很有可能優(yōu)先獲得這把鎖。內(nèi)建鎖無法實現(xiàn)公平機(jī)制,而lock體系可以實現(xiàn)公平鎖。
解決ABA問題
在JDK1.5后的atomic包中提供 了AtomicStampedReference來解決ABA問題, 它通過包裝[E,Integer]的元組來對對象標(biāo)記版本stamp,從而避免ABA問題。在了解AtomicStampedReference之前我們可以先分析一下AtomicReference。
import java.util.concurrent.atomic.AtomicReference;public class AtomicReferenceDemo {
public static void main(String[] args) {
AtomicReference<String> atomicReference = new AtomicReference<>();
atomicReference.set("AAA");
//CAS操作更新
boolean result = atomicReference.compareAndSet("AAA", "BBB");
System.out.println(result + " " + atomicReference.get());
//CAS操作更新
result = atomicReference.compareAndSet("AAA", "CCC");
System.out.println(result + " " + atomicReference.get());
}
}
AtomicReference的成員變量
//Unsafe類提供CAS操作private static final Unsafe unsafe = Unsafe.getUnsafe();
//value變量的偏移地址,這個偏移地址在static塊里初始化
private static final long valueOffset;
//實際傳入需要原子操作的那個類實例
private volatile V value;
compareAndSet方法是基于Unsafe提供的compareAndSwapObject方法, 這里的compareAndSet方法即CAS操作本身是原子的,但是在某些場景下會出現(xiàn)異常場景,也就是ABA問題。我們使用AtomicStampedReference來解決這個問題,下面是AtomicStampedReference的關(guān)鍵結(jié)構(gòu):
public class AtomicStampedReference<V> {private static class Pair<T> {
final T reference; //維護(hù)對象引用
final int stamp; //用于標(biāo)志版本
private Pair(T reference, int stamp) {
this.reference = reference;
this.stamp = stamp;
}
static <T> Pair<T> of(T reference, int stamp) {
return new Pair<T>(reference, stamp);
}
}
private volatile Pair<V> pair;
...
}
賞
謝謝你請我喝咖啡
支付寶 微信- 本文作者: Tim
- 本文鏈接: https://zouchanglin.cn/2020/04/02/892077598.html
- 版權(quán)聲明: 本博客所有文章除特別聲明外,均采用 CC BY-SA 4.0 許可協(xié)議。轉(zhuǎn)載請注明出處!
總結(jié)
以上是生活随笔為你收集整理的CAS操作与ABA问题的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 怎样在几何画板中输入因为符号
- 下一篇: Dubbo Zookeeper