成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

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

開發
本文從CAS的基本使用場景、基本流程、實現類AtomicInteger源碼解析、CAS的Unsafe實現解析、CAS的缺點及解決方案等方面來全面了解了CAS。

背景

在高并發的業務場景下,線程安全問題是必須考慮的,在JDK5之前,可以通過synchronized或Lock來保證同步,從而達到線程安全的目的。但synchronized或Lock方案屬于互斥鎖的方案,比較重量級,加鎖、釋放鎖都會引起性能損耗問題。

而在某些場景下,我們是可以通過JUC提供的CAS機制實現無鎖的解決方案,或者說是它基于類似于樂觀鎖的方案,來達到非阻塞同步的方式保證線程安全。

CAS機制不僅是面試中會高頻出現的面試題,而且也是高并發實踐中必須掌握的知識點。如果你目前對CAS還不甚了解,或許只有模糊的印象,這篇文章一定值得你花時間學習一下。

什么是CAS?

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

本質上來講CAS是一種無鎖的解決方案,也是一種基于樂觀鎖的操作,可以保證在多線程并發中保障共享資源的原子性操作,相對于synchronized或Lock來說,是一種輕量級的實現方案。

Java中大量使用了CAS機制來實現多線程下數據更新的原子化操作,比如AtomicInteger、CurrentHashMap當中都有CAS的應用。但Java中并沒有直接實現CAS,CAS相關的實現是借助C/C++調用CPU指令來實現的,效率很高,但Java代碼需通過JNI才能調用。比如,Unsafe類提供的CAS方法(如compareAndSwapXXX)底層實現即為CPU指令cmpxchg。

CAS的基本流程

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

圖片

圖片CAS操作流程圖

在上圖中涉及到三個值的比較和操作:修改之前獲取的(待修改)值A,業務邏輯計算的新值B,以及待修改值對應的內存位置的C。

整個處理流程中,假設內存中存在一個變量i,它在內存中對應的值是A(第一次讀取),此時經過業務處理之后,要把它更新成B,那么在更新之前會再讀取一下i現在的值C,如果在業務處理的過程中i的值并沒有發生變化,也就是A和C相同,才會把i更新(交換)為新值B。如果A和C不相同,那說明在業務計算時,i的值發生了變化,則不更新(交換)成B。最后,CPU會將舊的數值返回。而上述的一系列操作由CPU指令來保證是原子的。

在《Java并發編程實踐》中對CAS進行了更加通俗的描述:我認為原有的值應該是什么,如果是,則將原有的值更新為新值,否則不做修改,并告訴我原來的值是多少。

在上述路程中,我們可以很清晰的看到樂觀鎖的思路,而且這期間并沒有使用到鎖。因此,相對于synchronized等悲觀鎖的實現,效率要高非常多。

基于CAS的AtomicInteger使用

關于CAS的實現,最經典最常用的當屬AtomicInteger了,我們馬上就來看一下AtomicInteger是如何利用CAS實現原子性操作的。為了形成更新鮮明的對比,先來看一下如果不使用CAS機制,想實現線程安全我們通常如何處理。

在沒有使用CAS機制時,為了保證線程安全,基于synchronized的實現如下:

public class ThreadSafeTest {

public static volatile int i = 0;

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

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

那么,如果采用基于CAS實現的AtomicInteger類,上述方法的實現便變得簡單且輕量級了:

public class ThreadSafeTest {

private final AtomicInteger counter = new AtomicInteger(0);

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

}

之所以可以如此安全、便捷地來實現安全操作,便是由于AtomicInteger類采用了CAS機制。下面,我們就來了解一下AtomicInteger的功能及源碼實現。

CAS的AtomicInteger類

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

AtomicInteger常用API

AtomicInteger類提供了如下常見的API功能:

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

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

AtomicInteger核心源碼

下面看一下AtomicInteger代碼中的核心實現代碼:

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

private volatile int value;

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

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

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

上述代碼以AtomicInteger#incrementAndGet方法為例展示了AtomicInteger的基本實現。其中,在static靜態代碼塊中,基于Unsafe類獲取value字段相對當前對象的“起始地址”的偏移量,用于后續Unsafe類的處理。

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

同時,在AtomicInteger類中,可以看到value值通過volatile進行修飾,保證了該屬性值的線程可見性。在多并發的情況下,一個線程的修改,可以保證到其他線程立馬看到修改后的值。

通過源碼可以看出, AtomicInteger 底層是通過volatile變量和CAS兩者相結合來保證更新數據的原子性。其中關于Unsafe類對CAS的實現,我們下面詳細介紹。

CAS的工作原理

CAS的實現原理簡單來說就是由Unsafe類和其中的自旋鎖來完成的,下面針對源代碼來看一下這兩塊的內容。

UnSafe類

在AtomicInteger核心源碼中,已經看到CAS的實現是通過Unsafe類來完成的,先來了解一下Unsafe類的作用。

sun.misc.Unsafe是JDK內部用的工具類。它通過暴露一些Java意義上說“不安全”的功能給Java層代碼,來讓JDK能夠更多的使用Java代碼來實現一些原本是平臺相關的、需要使用native語言(例如C或C++)才可以實現的功能。該類不應該在JDK核心類庫之外使用,這也是命名為Unsafe(不安全)的原因。

JVM的實現可以自由選擇如何實現Java對象的“布局”,也就是在內存里Java對象的各個部分放在哪里,包括對象的實例字段和一些元數據之類。

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

Unsafe類的功能主要分為內存操作、CAS、Class相關、對象操作、數組相關、內存屏障、系統相關、線程調度等功能。這里我們只需要知道其功能即可,方便理解CAS的實現,注意不建議在日常開發中使用。

Unsafe與CAS

AtomicInteger調用了Unsafe#getAndAddInt方法:

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

上述代碼等于是AtomicInteger調用UnSafe類的CAS方法,JVM幫我們實現出匯編指令,從而實現原子操作。

在Unsafe中getAndAddInt方法實現如下:

 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方法有三個參數:

  • 第一個參數表示當前對象,也就是new的那個AtomicInteger對象;
  • 第二個表示內存地址;
  • 第三個表示自增步伐,在AtomicInteger#incrementAndGet中默認的自增步伐是1。

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

在上述邏輯中核心方法是compareAndSwapInt方法,它是一個native方法,這個方法匯編之后是CPU原語指令,原語指令是連續執行不會被打斷的,所以可以保證原子性。

在getAndAddInt方法中還涉及到一個實現自旋鎖。所謂的自旋,其實就是上面getAndAddInt方法中的do while循環操作。當預期值和主內存中的值不等時,就重新獲取主內存中的值,這就是自旋。

這里我們可以看到CAS實現的一個缺點:內部使用自旋的方式進行CAS更新(while循環進行CAS更新,如果更新失敗,則循環再次重試)。如果長時間都不成功的話,就會造成CPU極大的開銷。

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

CAS的缺點

CAS高效地實現了原子性操作,但在以下三方面還存在著一些缺點:

  • 循環時間長,開銷大;
  • 只能保證一個共享變量的原子操作;
  • ABA問題;

下面就這個三個問題詳細討論一下。

循環時間長開銷大

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

只能保證一個共享變量的原子操作

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

ABA問題

雖然使用CAS可以實現非阻塞式的原子性操作,但是會產生ABA問題,ABA問題出現的基本流程:

  • 進程P1在共享變量中讀到值為A;
  • P1被搶占了,進程P2執行;
  • P2把共享變量里的值從A改成了B,再改回到A,此時被P1搶占;
  • P1回來看到共享變量里的值沒有被改變,于是繼續執行;

雖然P1以為變量值沒有改變,繼續執行了,但是這個會引發一些潛在的問題。ABA問題最容易發生在lock free的算法中的,CAS首當其沖,因為CAS判斷的是指針的地址。如果這個地址被重用了呢,問題就很大了(地址被重用是很經常發生的,一個內存分配后釋放了,再分配,很有可能還是原來的地址)。

維基百科上給了一個形象的例子:你拿著一個裝滿錢的手提箱在飛機場,此時過來了一個火辣性感的美女,然后她很暖昧地挑逗著你,并趁你不注意,把用一個一模一樣的手提箱和你那裝滿錢的箱子調了個包,然后就離開了,你看到你的手提箱還在那,于是就提著手提箱去趕飛機去了。

ABA問題的解決思路就是使用版本號:在變量前面追加上版本號,每次變量更新的時候把版本號加1,那么A->B->A就會變成1A->2B->3A。

另外,從Java 1.5開始,JDK的Atomic包里提供了一個類AtomicStampedReference來解決ABA問題。這個類的compareAndSet方法的作用是首先檢查當前引用是否等于預期引用,并且檢查當前標志是否等于預期標志,如果全部相等,則以原子方式將該引用和該標志的值設置為給定的更新值。

責任編輯:趙寧寧 來源: 程序新視界
相關推薦

2022-06-30 16:03:28

Spring事務傳播

2024-12-16 17:12:43

2019-08-14 10:17:14

Java數據結構文章

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

2024-02-29 09:08:56

Encoding算法加密

2020-09-10 16:10:17

js繼承模式前端

2021-07-27 07:31:16

JavaArrayList數組

2022-05-15 21:52:04

typeTypeScriptinterface

2022-09-26 10:09:08

MVCC控制并發

2018-12-17 12:30:05

Kubernetes存儲存儲卷

2019-08-09 15:03:53

2019-01-08 07:43:53

路由器調制解調器

2020-10-30 08:20:04

SD卡TF卡存儲

2012-08-27 13:44:01

Google

2022-08-04 09:39:39

Kubernetes聲明式系統

2019-07-01 15:01:44

NVMe接口存儲

2023-07-13 09:05:57

react hook類型types
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 国产精品视频在线播放 | 中文字幕福利 | 中文字幕1区 | 国产精品精品 | av大片在线观看 | 亚洲精品国产a久久久久久 中文字幕一区二区三区四区五区 | 一区二区av在线 | 日韩午夜网站 | 99re热精品视频 | 国产成人福利 | 在线观看 亚洲 | 国产精品99久久久久久大便 | 亚洲综合一区二区三区 | 国产精品久久久久久中文字 | 免费在线成人网 | 免费看黄色小视频 | 国产综合精品 | 亚洲精品中文字幕中文字幕 | 久久99精品久久久97夜夜嗨 | 丝袜一区二区三区 | 狠狠操狠狠干 | 国产在线成人 | 欧美成人精品一区二区男人看 | 先锋资源站 | 国产精品久久久久一区二区 | 欧美激情精品久久久久 | 亚洲黄色一级 | 久久国产一区 | 成人无遮挡毛片免费看 | 麻豆av网站 | 久久com | 狠狠亚洲| 免费高清成人 | 伊人久久国产 | 一区二区三区四区电影视频在线观看 | 午夜男人天堂 | 国产高清免费在线 | 欧美激情久久久久久 | 成人精品视频在线观看 | 成人日韩精品 | 亚洲国产午夜 |