自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

什么是CAS?如果說(shuō)不清楚,這篇文章要讀一讀!

開(kāi)發(fā)
本文從CAS的基本使用場(chǎng)景、基本流程、實(shí)現(xiàn)類AtomicInteger源碼解析、CAS的Unsafe實(shí)現(xiàn)解析、CAS的缺點(diǎn)及解決方案等方面來(lái)全面了解了CAS。

背景

在高并發(fā)的業(yè)務(wù)場(chǎng)景下,線程安全問(wèn)題是必須考慮的,在JDK5之前,可以通過(guò)synchronized或Lock來(lái)保證同步,從而達(dá)到線程安全的目的。但synchronized或Lock方案屬于互斥鎖的方案,比較重量級(jí),加鎖、釋放鎖都會(huì)引起性能損耗問(wèn)題。

而在某些場(chǎng)景下,我們是可以通過(guò)JUC提供的CAS機(jī)制實(shí)現(xiàn)無(wú)鎖的解決方案,或者說(shuō)是它基于類似于樂(lè)觀鎖的方案,來(lái)達(dá)到非阻塞同步的方式保證線程安全。

CAS機(jī)制不僅是面試中會(huì)高頻出現(xiàn)的面試題,而且也是高并發(fā)實(shí)踐中必須掌握的知識(shí)點(diǎn)。如果你目前對(duì)CAS還不甚了解,或許只有模糊的印象,這篇文章一定值得你花時(shí)間學(xué)習(xí)一下。

什么是CAS?

CAS是Compare And Swap的縮寫,直譯就是比較并交換。CAS是現(xiàn)代CPU廣泛支持的一種對(duì)內(nèi)存中的共享數(shù)據(jù)進(jìn)行操作的一種特殊指令,這個(gè)指令會(huì)對(duì)內(nèi)存中的共享數(shù)據(jù)做原子的讀寫操作。其作用是讓CPU比較內(nèi)存中某個(gè)值是否和預(yù)期的值相同,如果相同則將這個(gè)值更新為新值,不相同則不做更新。

本質(zhì)上來(lái)講CAS是一種無(wú)鎖的解決方案,也是一種基于樂(lè)觀鎖的操作,可以保證在多線程并發(fā)中保障共享資源的原子性操作,相對(duì)于synchronized或Lock來(lái)說(shuō),是一種輕量級(jí)的實(shí)現(xiàn)方案。

Java中大量使用了CAS機(jī)制來(lái)實(shí)現(xiàn)多線程下數(shù)據(jù)更新的原子化操作,比如AtomicInteger、CurrentHashMap當(dāng)中都有CAS的應(yīng)用。但Java中并沒(méi)有直接實(shí)現(xiàn)CAS,CAS相關(guān)的實(shí)現(xiàn)是借助C/C++調(diào)用CPU指令來(lái)實(shí)現(xiàn)的,效率很高,但Java代碼需通過(guò)JNI才能調(diào)用。比如,Unsafe類提供的CAS方法(如compareAndSwapXXX)底層實(shí)現(xiàn)即為CPU指令cmpxchg。

CAS的基本流程

下面我們用一張圖來(lái)了解一下CAS操作的基本流程。

圖片

圖片CAS操作流程圖

在上圖中涉及到三個(gè)值的比較和操作:修改之前獲取的(待修改)值A(chǔ),業(yè)務(wù)邏輯計(jì)算的新值B,以及待修改值對(duì)應(yīng)的內(nèi)存位置的C。

整個(gè)處理流程中,假設(shè)內(nèi)存中存在一個(gè)變量i,它在內(nèi)存中對(duì)應(yīng)的值是A(第一次讀取),此時(shí)經(jīng)過(guò)業(yè)務(wù)處理之后,要把它更新成B,那么在更新之前會(huì)再讀取一下i現(xiàn)在的值C,如果在業(yè)務(wù)處理的過(guò)程中i的值并沒(méi)有發(fā)生變化,也就是A和C相同,才會(huì)把i更新(交換)為新值B。如果A和C不相同,那說(shuō)明在業(yè)務(wù)計(jì)算時(shí),i的值發(fā)生了變化,則不更新(交換)成B。最后,CPU會(huì)將舊的數(shù)值返回。而上述的一系列操作由CPU指令來(lái)保證是原子的。

在《Java并發(fā)編程實(shí)踐》中對(duì)CAS進(jìn)行了更加通俗的描述:我認(rèn)為原有的值應(yīng)該是什么,如果是,則將原有的值更新為新值,否則不做修改,并告訴我原來(lái)的值是多少。

在上述路程中,我們可以很清晰的看到樂(lè)觀鎖的思路,而且這期間并沒(méi)有使用到鎖。因此,相對(duì)于synchronized等悲觀鎖的實(shí)現(xiàn),效率要高非常多。

基于CAS的AtomicInteger使用

關(guān)于CAS的實(shí)現(xiàn),最經(jīng)典最常用的當(dāng)屬AtomicInteger了,我們馬上就來(lái)看一下AtomicInteger是如何利用CAS實(shí)現(xiàn)原子性操作的。為了形成更新鮮明的對(duì)比,先來(lái)看一下如果不使用CAS機(jī)制,想實(shí)現(xiàn)線程安全我們通常如何處理。

在沒(méi)有使用CAS機(jī)制時(shí),為了保證線程安全,基于synchronized的實(shí)現(xiàn)如下:

public class ThreadSafeTest {

public static volatile int i = 0;

public synchronized void increase() {
i++;
}
}

至于上面的實(shí)例具體實(shí)現(xiàn),這里不再展開(kāi),很多相關(guān)的文章專門進(jìn)行講解,我們只需要知道為了保證i++的原子操作,在increase方法上使用了重量級(jí)的鎖synchronized,這會(huì)導(dǎo)致該方法的性能低下,所有調(diào)用該方法的操作都需要同步等待處理。

那么,如果采用基于CAS實(shí)現(xiàn)的AtomicInteger類,上述方法的實(shí)現(xiàn)便變得簡(jiǎn)單且輕量級(jí)了:

public class ThreadSafeTest {

private final AtomicInteger counter = new AtomicInteger(0);

public int increase(){
return counter.addAndGet(1);
}

}

之所以可以如此安全、便捷地來(lái)實(shí)現(xiàn)安全操作,便是由于AtomicInteger類采用了CAS機(jī)制。下面,我們就來(lái)了解一下AtomicInteger的功能及源碼實(shí)現(xiàn)。

CAS的AtomicInteger類

AtomicInteger?是java.util.concurrent.atomic 包下的一個(gè)原子類,該包下還有AtomicBoolean?, AtomicLong,AtomicLongArray?, AtomicReference等原子類,主要用于在高并發(fā)環(huán)境下,保證線程安全。

AtomicInteger常用API

AtomicInteger類提供了如下常見(jiàn)的API功能:

public final int get():獲取當(dāng)前的值
public final int getAndSet(int newValue):獲取當(dāng)前的值,并設(shè)置新的值
public final int getAndIncrement():獲取當(dāng)前的值,并自增
public final int getAndDecrement():獲取當(dāng)前的值,并自減
public final int getAndAdd(int delta):獲取當(dāng)前的值,并加上預(yù)期的值
void lazySet(int newValue): 最終會(huì)設(shè)置成newValue,使用lazySet設(shè)置值后,可能導(dǎo)致其他線程在之后的一小段時(shí)間內(nèi)還是可以讀到舊的值。

上述方法中,getAndXXX格式的方法都實(shí)現(xiàn)了原子操作。具體的使用方法參考上面的addAndGet案例即可。

AtomicInteger核心源碼

下面看一下AtomicInteger代碼中的核心實(shí)現(xiàn)代碼:

public class AtomicInteger extends Number implements java.io.Serializable {
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
static {
try {
// 用于獲取value字段相對(duì)當(dāng)前對(duì)象的“起始地址”的偏移量
valueOffset = unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}

private volatile int value;

//返回當(dāng)前值
public final int get() {
return value;
}

//遞增加detla
public final int getAndAdd(int delta) {
// 1、this:當(dāng)前的實(shí)例
// 2、valueOffset:value實(shí)例變量的偏移量
// 3、delta:當(dāng)前value要加上的數(shù)(value+delta)。
return unsafe.getAndAddInt(this, valueOffset, delta);
}

//遞增加1
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
...
}

上述代碼以AtomicInteger#incrementAndGet方法為例展示了AtomicInteger的基本實(shí)現(xiàn)。其中,在static靜態(tài)代碼塊中,基于Unsafe類獲取value字段相對(duì)當(dāng)前對(duì)象的“起始地址”的偏移量,用于后續(xù)Unsafe類的處理。

在處理自增的原子操作時(shí),使用的是Unsafe類中的getAndAddInt方法,CAS的實(shí)現(xiàn)便是由Unsafe類的該方法提供,從而保證自增操作的原子性。

同時(shí),在AtomicInteger類中,可以看到value值通過(guò)volatile進(jìn)行修飾,保證了該屬性值的線程可見(jiàn)性。在多并發(fā)的情況下,一個(gè)線程的修改,可以保證到其他線程立馬看到修改后的值。

通過(guò)源碼可以看出, AtomicInteger 底層是通過(guò)volatile變量和CAS兩者相結(jié)合來(lái)保證更新數(shù)據(jù)的原子性。其中關(guān)于Unsafe類對(duì)CAS的實(shí)現(xiàn),我們下面詳細(xì)介紹。

CAS的工作原理

CAS的實(shí)現(xiàn)原理簡(jiǎn)單來(lái)說(shuō)就是由Unsafe類和其中的自旋鎖來(lái)完成的,下面針對(duì)源代碼來(lái)看一下這兩塊的內(nèi)容。

UnSafe類

在AtomicInteger核心源碼中,已經(jīng)看到CAS的實(shí)現(xiàn)是通過(guò)Unsafe類來(lái)完成的,先來(lái)了解一下Unsafe類的作用。

sun.misc.Unsafe是JDK內(nèi)部用的工具類。它通過(guò)暴露一些Java意義上說(shuō)“不安全”的功能給Java層代碼,來(lái)讓JDK能夠更多的使用Java代碼來(lái)實(shí)現(xiàn)一些原本是平臺(tái)相關(guān)的、需要使用native語(yǔ)言(例如C或C++)才可以實(shí)現(xiàn)的功能。該類不應(yīng)該在JDK核心類庫(kù)之外使用,這也是命名為Unsafe(不安全)的原因。

JVM的實(shí)現(xiàn)可以自由選擇如何實(shí)現(xiàn)Java對(duì)象的“布局”,也就是在內(nèi)存里Java對(duì)象的各個(gè)部分放在哪里,包括對(duì)象的實(shí)例字段和一些元數(shù)據(jù)之類。

Unsafe里關(guān)于對(duì)象字段訪問(wèn)的方法把對(duì)象布局抽象出來(lái),它提供了objectFieldOffset()方法用于獲取某個(gè)字段相對(duì)Java對(duì)象的“起始地址”的偏移量,也提供了getInt、getLong、getObject之類的方法可以使用前面獲取的偏移量來(lái)訪問(wèn)某個(gè)Java對(duì)象的某個(gè)字段。在AtomicInteger的static代碼塊中便使用了objectFieldOffset()方法。

Unsafe類的功能主要分為內(nèi)存操作、CAS、Class相關(guān)、對(duì)象操作、數(shù)組相關(guān)、內(nèi)存屏障、系統(tǒng)相關(guān)、線程調(diào)度等功能。這里我們只需要知道其功能即可,方便理解CAS的實(shí)現(xiàn),注意不建議在日常開(kāi)發(fā)中使用。

Unsafe與CAS

AtomicInteger調(diào)用了Unsafe#getAndAddInt方法:

    public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}

上述代碼等于是AtomicInteger調(diào)用UnSafe類的CAS方法,JVM幫我們實(shí)現(xiàn)出匯編指令,從而實(shí)現(xiàn)原子操作。

在Unsafe中g(shù)etAndAddInt方法實(shí)現(xiàn)如下:

 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;
}

getAndAddInt方法有三個(gè)參數(shù):

  • 第一個(gè)參數(shù)表示當(dāng)前對(duì)象,也就是new的那個(gè)AtomicInteger對(duì)象;
  • 第二個(gè)表示內(nèi)存地址;
  • 第三個(gè)表示自增步伐,在AtomicInteger#incrementAndGet中默認(rèn)的自增步伐是1。

getAndAddInt方法中,首先把當(dāng)前對(duì)象主內(nèi)存中的值賦給val5,然后進(jìn)入while循環(huán)。判斷當(dāng)前對(duì)象此刻主內(nèi)存中的值是否等于val5,如果是,就自增(交換值),否則繼續(xù)循環(huán),重新獲取val5的值。

在上述邏輯中核心方法是compareAndSwapInt方法,它是一個(gè)native方法,這個(gè)方法匯編之后是CPU原語(yǔ)指令,原語(yǔ)指令是連續(xù)執(zhí)行不會(huì)被打斷的,所以可以保證原子性。

在getAndAddInt方法中還涉及到一個(gè)實(shí)現(xiàn)自旋鎖。所謂的自旋,其實(shí)就是上面getAndAddInt方法中的do while循環(huán)操作。當(dāng)預(yù)期值和主內(nèi)存中的值不等時(shí),就重新獲取主內(nèi)存中的值,這就是自旋。

這里我們可以看到CAS實(shí)現(xiàn)的一個(gè)缺點(diǎn):內(nèi)部使用自旋的方式進(jìn)行CAS更新(while循環(huán)進(jìn)行CAS更新,如果更新失敗,則循環(huán)再次重試)。如果長(zhǎng)時(shí)間都不成功的話,就會(huì)造成CPU極大的開(kāi)銷。

另外,Unsafe類還支持了其他的CAS方法,比如compareAndSwapObject、 compareAndSwapInt、compareAndSwapLong?。

CAS的缺點(diǎn)

CAS高效地實(shí)現(xiàn)了原子性操作,但在以下三方面還存在著一些缺點(diǎn):

  • 循環(huán)時(shí)間長(zhǎng),開(kāi)銷大;
  • 只能保證一個(gè)共享變量的原子操作;
  • ABA問(wèn)題;

下面就這個(gè)三個(gè)問(wèn)題詳細(xì)討論一下。

循環(huán)時(shí)間長(zhǎng)開(kāi)銷大

在分析Unsafe源代碼的時(shí)候我們已經(jīng)提到,在Unsafe的實(shí)現(xiàn)中使用了自旋鎖的機(jī)制。在該環(huán)節(jié)如果CAS?操作失敗,就需要循環(huán)進(jìn)行CAS操作(do while循環(huán)同時(shí)將期望值更新為最新的),如果長(zhǎng)時(shí)間都不成功的話,那么會(huì)造成CPU極大的開(kāi)銷。如果JVM能支持處理器提供的pause指令那么效率會(huì)有一定的提升。

只能保證一個(gè)共享變量的原子操作

在最初的實(shí)例中,可以看出是針對(duì)一個(gè)共享變量使用了CAS機(jī)制,可以保證原子性操作。但如果存在多個(gè)共享變量,或一整個(gè)代碼塊的邏輯需要保證線程安全,CAS就無(wú)法保證原子性操作了,此時(shí)就需要考慮采用加鎖方式(悲觀鎖)保證原子性,或者有一個(gè)取巧的辦法,把多個(gè)共享變量合并成一個(gè)共享變量進(jìn)行CAS操作。

ABA問(wèn)題

雖然使用CAS可以實(shí)現(xiàn)非阻塞式的原子性操作,但是會(huì)產(chǎn)生ABA問(wèn)題,ABA問(wèn)題出現(xiàn)的基本流程:

  • 進(jìn)程P1在共享變量中讀到值為A;
  • P1被搶占了,進(jìn)程P2執(zhí)行;
  • P2把共享變量里的值從A改成了B,再改回到A,此時(shí)被P1搶占;
  • P1回來(lái)看到共享變量里的值沒(méi)有被改變,于是繼續(xù)執(zhí)行;

雖然P1以為變量值沒(méi)有改變,繼續(xù)執(zhí)行了,但是這個(gè)會(huì)引發(fā)一些潛在的問(wèn)題。ABA問(wèn)題最容易發(fā)生在lock free的算法中的,CAS首當(dāng)其沖,因?yàn)镃AS判斷的是指針的地址。如果這個(gè)地址被重用了呢,問(wèn)題就很大了(地址被重用是很經(jīng)常發(fā)生的,一個(gè)內(nèi)存分配后釋放了,再分配,很有可能還是原來(lái)的地址)。

維基百科上給了一個(gè)形象的例子:你拿著一個(gè)裝滿錢的手提箱在飛機(jī)場(chǎng),此時(shí)過(guò)來(lái)了一個(gè)火辣性感的美女,然后她很暖昧地挑逗著你,并趁你不注意,把用一個(gè)一模一樣的手提箱和你那裝滿錢的箱子調(diào)了個(gè)包,然后就離開(kāi)了,你看到你的手提箱還在那,于是就提著手提箱去趕飛機(jī)去了。

ABA問(wèn)題的解決思路就是使用版本號(hào):在變量前面追加上版本號(hào),每次變量更新的時(shí)候把版本號(hào)加1,那么A->B->A就會(huì)變成1A->2B->3A。

另外,從Java 1.5開(kāi)始,JDK的Atomic包里提供了一個(gè)類AtomicStampedReference來(lái)解決ABA問(wèn)題。這個(gè)類的compareAndSet方法的作用是首先檢查當(dāng)前引用是否等于預(yù)期引用,并且檢查當(dāng)前標(biāo)志是否等于預(yù)期標(biāo)志,如果全部相等,則以原子方式將該引用和該標(biāo)志的值設(shè)置為給定的更新值。

責(zé)任編輯:趙寧寧 來(lái)源: 程序新視界
相關(guān)推薦

2022-06-30 16:03:28

Spring事務(wù)傳播

2024-12-16 17:12:43

2019-08-14 10:17:14

Java數(shù)據(jù)結(jié)構(gòu)文章

2022-07-21 21:19:48

元宇宙

2021-03-10 08:56:37

Zookeeper

2020-12-10 13:46:35

人工智能

2020-05-06 19:47:15

人工智能AI

2020-09-10 16:10:17

js繼承模式前端

2021-07-27 07:31:16

JavaArrayList數(shù)組

2022-05-15 21:52:04

typeTypeScriptinterface

2024-02-29 09:08:56

Encoding算法加密

2022-09-26 10:09:08

MVCC控制并發(fā)

2020-10-30 08:20:04

SD卡TF卡存儲(chǔ)

2018-12-17 12:30:05

Kubernetes存儲(chǔ)存儲(chǔ)卷

2019-01-08 07:43:53

路由器調(diào)制解調(diào)器

2023-08-07 11:07:30

5G電信TECS

2022-08-04 09:39:39

Kubernetes聲明式系統(tǒng)

2012-08-27 13:44:01

Google

2019-07-01 15:01:44

NVMe接口存儲(chǔ)

2018-05-22 16:24:20

HashMapJavaJDK
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)