<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

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

          共 8666字,需瀏覽 18分鐘

           ·

          2022-07-26 11:19

          背景

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

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

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

          什么是CAS?

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

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

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

          CAS的基本流程

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

          CAS操作流程圖

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

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

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

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

          基于CAS的AtomicInteger使用

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

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

          public class ThreadSafeTest {

           public static volatile int i = 0;

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

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

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

          public class ThreadSafeTest {

           private final AtomicInteger counter = new AtomicInteger(0);

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

          }

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

          CAS的AtomicInteger類

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

          AtomicInteger常用API

          AtomicInteger類提供了如下常見的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): 最終會設(shè)置成newValue,使用lazySet設(shè)置值后,可能導(dǎo)致其他線程在之后的一小段時間內(nèi)還是可以讀到舊的值。

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

          AtomicInteger核心源碼

          下面看一下AtomicInteger代碼中的核心實現(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字段相對當(dāng)前對象的“起始地址”的偏移量
                      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)前的實例 
                  // 2、valueOffset:value實例變量的偏移量 
                  // 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的基本實現(xiàn)。其中,在static靜態(tài)代碼塊中,基于Unsafe類獲取value字段相對當(dāng)前對象的“起始地址”的偏移量,用于后續(xù)Unsafe類的處理。

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

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

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

          CAS的工作原理

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

          UnSafe類

          在AtomicInteger核心源碼中,已經(jīng)看到CAS的實現(xiàn)是通過Unsafe類來完成的,先來了解一下Unsafe類的作用。關(guān)于Unsafe類在之前的文章《各大框架都在使用的Unsafe類,到底有多神奇?》也有詳細的介紹,大家可以參考,這里我們再簡單概述一下。

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

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

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

          Unsafe類的功能主要分為內(nèi)存操作、CAS、Class相關(guān)、對象操作、數(shù)組相關(guān)、內(nèi)存屏障、系統(tǒng)相關(guān)、線程調(diào)度等功能。這里我們只需要知道其功能即可,方便理解CAS的實現(xiàn),注意不建議在日常開發(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幫我們實現(xiàn)出匯編指令,從而實現(xiàn)原子操作。

          在Unsafe中g(shù)etAndAddInt方法實現(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方法有三個參數(shù):

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

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

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

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

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

          另外,Unsafe類還支持了其他的CAS方法,比如compareAndSwapObject、 compareAndSwapInt、compareAndSwapLong。更多關(guān)于Unsafe類的功能就不再展開,大家可以去看《各大框架都在使用的Unsafe類,到底有多神奇?》這篇文章。

          CAS的缺點

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

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

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

          循環(huán)時間長開銷大

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

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

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

          ABA問題

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

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

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

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

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

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

          小結(jié)

          本文從CAS的基本使用場景、基本流程、實現(xiàn)類AtomicInteger源碼解析、CAS的Unsafe實現(xiàn)解析、CAS的缺點及解決方案等方面來全面了解了CAS。通過這篇文章的學(xué)習(xí)想必你已經(jīng)更加深刻的理解CAS機制了,如果對你有所幫助,記得關(guān)注一下,持續(xù)輸出干貨內(nèi)容。


          要搞明白優(yōu)惠券架構(gòu)是如何演化的,只需10張圖!

          2022-07-20

          不掌握這些坑,你敢用BigDecimal嗎?

          2022-07-19

          緩存穿透、緩存雪崩、緩存擊穿?再也不怕了,你隨便問吧!

          2022-07-11

          不知道這4種緩存模式,敢說懂緩存嗎?

          2022-07-04

          12張圖帶你徹底理解Java新的垃圾收集器ZGC

          2022-06-27




          如果你覺得這篇文章不錯,那么,下篇通常會更好。備注“公眾號”添加微信好友(微信號:zhuan2quan)。

          ▲ 按關(guān)注”程序新視界“,洞察技術(shù)內(nèi)幕


          瀏覽 51
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  操屄视频欧美日韩在线 | 国产又粗又长又硬免费视频 | 欧美日韩色视 | 九九视频免费观看 | www.我操 |