91超碰碰碰碰久久久久久综合_超碰av人澡人澡人澡人澡人掠_国产黄大片在线观看画质优化_txt小说免费全本

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

并發編程的靈魂:CAS機制詳解

發布時間:2020-04-05 07:43:33 來源:網絡 閱讀:365 作者:Java_老男孩 欄目:編程語言

Java中提供了很多原子操作類來保證共享變量操作的原子性。這些原子操作的底層原理都是使用了CAS機制。在使用一門技術之前,了解這個技術的底層原理是非常重要的,所以本篇文章就先來講講什么是CAS機制,CAS機制存在的一些問題以及在Java中怎么使用CAS機制。

其實Java并發框架的基石一共有兩塊,一塊是本文介紹的CAS,另一塊就是AQS,后續也會寫文章介紹。

什么是CAS機制

CAS機制是一種數據更新的方式。在具體講什么是CAS機制之前,我們先來聊下在多線程環境下,對共享變量進行數據更新的兩種模式:悲觀鎖模式和樂觀鎖模式。

悲觀鎖更新的方式認為:在更新數據的時候大概率會有其他線程去爭奪共享資源,所以悲觀鎖的做法是:第一個獲取資源的線程會將資源鎖定起來,其他沒爭奪到資源的線程只能進入阻塞隊列,等第一個獲取資源的線程釋放鎖之后,這些線程才能有機會重新爭奪資源。synchronized就是java中悲觀鎖的典型實現,synchronized使用起來非常簡單方便,但是會使沒爭搶到資源的線程進入阻塞狀態,線程在阻塞狀態和Runnable狀態之間切換效率較低(比較慢)。比如你的更新操作其實是非常快的,這種情況下你還用synchronized將其他線程都鎖住了,線程從Blocked狀態切換回Runnable華的時間可能比你的更新操作的時間還要長。

樂觀鎖更新方式認為:在更新數據的時候其他線程爭搶這個共享變量的概率非常小,所以更新數據的時候不會對共享數據加鎖。但是在正式更新數據之前會檢查數據是否被其他線程改變過,如果未被其他線程改變過就將共享變量更新成最新值,如果發現共享變量已經被其他線程更新過了,就重試,直到成功為止。CAS機制就是樂觀鎖的典型實現。

CAS,是Compare and Swap的簡稱,在這個機制中有三個核心的參數:

  • 主內存中存放的共享變量的值:V(一般情況下這個V是內存的地址值,通過這個地址可以獲得內存中的值)
  • 工作內存中共享變量的副本值,也叫預期值:A
  • 需要將共享變量更新到的最新值:B

并發編程的靈魂:CAS機制詳解

如上圖中,主存中保存V值,線程中要使用V值要先從主存中讀取V值到線程的工作內存A中,然后計算后變成B值,最后再把B值寫回到內存V值中。多個線程共用V值都是如此操作。CAS的核心是在將B值寫入到V之前要比較A值和V值是否相同,如果不相同證明此時V值已經被其他線程改變,重新將V值賦給A,并重新計算得到B,如果相同,則將B值賦給V。

值得注意的是CAS機制中的這步步驟是原子性的(從指令層面提供的原子操作),所以CAS機制可以解決多線程并發編程對共享變量讀寫的原子性問題。

CAS機制優缺點

缺點

1. ABA問題
ABA問題:CAS在操作的時候會檢查變量的值是否被更改過,如果沒有則更新值,但是帶來一個問題,最開始的值是A,接著變成B,最后又變成了A。經過檢查這個值確實沒有修改過,因為最后的值還是A,但是實際上這個值確實已經被修改過了。為了解決這個問題,在每次進行操作的時候加上一個版本號,每次操作的就是兩個值,一個版本號和某個值,A——>B——>A問題就變成了1A——>2B——>3A。在jdk中提供了AtomicStampedReference類解決ABA問題,用Pair這個內部類實現,包含兩個屬性,分別代表版本號和引用,在compareAndSet中先對當前引用進行檢查,再對版本號標志進行檢查,只有全部相等才更新值。

2. 可能會消耗較高的CPU
看起來CAS比鎖的效率高,從阻塞機制變成了非阻塞機制,減少了線程之間等待的時間。每個方法不能絕對的比另一個好,在線程之間競爭程度大的時候,如果使用CAS,每次都有很多的線程在競爭,也就是說CAS機制不能更新成功。這種情況下CAS機制會一直重試,這樣就會比較耗費CPU。因此可以看出,如果線程之間競爭程度小,使用CAS是一個很好的選擇;但是如果競爭很大,使用鎖可能是個更好的選擇。在并發量非常高的環境中,如果仍然想通過原子類來更新的話,可以使用AtomicLong的替代類:LongAdder。

3. 不能保證代碼塊的原子性
Java中的CAS機制只能保證共享變量操作的原子性,而不能保證代碼塊的原子性。

優點

  • 可以保證變量操作的原子性;
  • 并發量不是很高的情況下,使用CAS機制比使用鎖機制效率更高;
  • 在線程對共享資源占用時間較短的情況下,使用CAS機制效率也會較高。

Java提供的CAS操作類--Unsafe類

從Java5開始引入了對CAS機制的底層的支持,在這之前需要開發人員編寫相關的代碼才可以實現CAS。在原子變量類Atomic中(例如AtomicInteger、AtomicLong)可以看到CAS操作的代碼,在這里的代碼都是調用了底層(核心代碼調用native修飾的方法)的實現方法。在AtomicInteger源碼中可以看getAndSet方法和compareAndSet方法之間的關系,compareAndSet方法調用了底層的實現,該方法可以實現與一個volatile變量的讀取和寫入相同的效果。在前面說到了volatile不支持例如i++這樣的復合操作,在Atomic中提供了實現該操作的方法。JVM對CAS的支持通過這些原子類(Atomic***)暴露出來,供我們使用。

而Atomic系類的類底層調用的是Unsafe類的API,Unsafe類提供了一系列的compareAndSwap*方法,下面就簡單介紹下Unsafe類的API:

  • long objectFieldOffset(Field field)方法:返回指定的變量在所屬類中的內存偏移地址,該偏移地址僅僅在該Unsafe函數中訪問指定字段時使用。如下代碼使用Unsafe類獲取變量value在AtomicLong對象中的內存偏移。
    static {
    try {
       valueOffset = unsafe.objectFieldOffset
           (AtomicInteger.class.getDeclaredField("value"));
    } catch (Exception ex) { throw new Error(ex); }
    }
  • int arrayBaseOffset(Class arrayClass)方法:獲取數組中第一個元素的地址。
  • int arrayIndexScale(Class arrayClass)方法:獲取數組中一個元素占用的字節。
  • boolean compareAndSwapLong(Object obj, long offset, long expect, long update)方法:比較對象obj中偏移量為offset的變量的值是否與expect相等,相等則使用update值更新,然后返回true,否則返回false。
  • public native long getLongvolatile(Object obj, long offset)方法:獲取對象obj中偏移量為offset的變量對應volatile語義的值。
  • void putLongvolatile(Object obj, long offset, long value)方法:設置obj對象中offset偏移的類型為long的field的值為value,支持volatile語義。
  • void putOrderedLong(Object obj, long offset, long value)方法:設置obj對象中offset偏移地址對應的long型field的值為value。這是一個有延遲的putLongvolatile方法,并且不保證值修改對其他線程立刻可見。只有在變量使用volatile修飾并且預計會被意外修改時才使用該方法。
  • void park(boolean isAbsolute, long time)方法:阻塞當前線程,其中參數isAbsolute等于false且time等于0表示一直阻塞。time大于0表示等待指定的time后阻塞線程會被喚醒,這個time是個相對值,是個增量值,也就是相對當前時間累加time后當前線程就會被喚醒。如果isAbsolute等于true,并且time大于0,則表示阻塞的線程到指定的時間點后會被喚醒,這里time是個絕對時間,是將某個時間點換算為ms后的值。另外,當其他線程調用了當前阻塞線程的interrupt方法而中斷了當前線程時,當前線程也會返回,而當其他線程調用了unPark方法并且把當前線程作為參數時當前線程也會返回。
  • void unpark(Object thread)方法:喚醒調用park后阻塞的線程。

下面是JDK8新增的函數,這里只列出Long類型操作。

  • long getAndSetLong(Object obj, long offset, long update)方法:獲取對象obj中偏移量為offset的變量volatile語義的當前值,并設置變量volatile語義的值為update。

    //這個方法只是封裝了compareAndSwapLong的使用,不需要自己寫重試機制
    public final long getAndSetLong(Object var1, long var2, long var4) {
    long var6;
    do {
        var6 = this.getLongVolatile(var1, var2);
    } while(!this.compareAndSwapLong(var1, var2, var6, var4));
    
    return var6;
    }
  • long getAndAddLong(Object obj, long offset, long addValue)方法:獲取對象obj中偏移量為offset的變量volatile語義的當前值,并設置變量值為原始值+addValue,原理和上面的方法類似。

CAS使用場景

  • 使用一個變量統計網站的訪問量;
  • Atomic類操作;
  • 數據庫樂觀鎖更新。
向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

客服| 平舆县| 乐都县| 苏州市| 土默特右旗| 黄冈市| 乐平市| 武威市| 奉新县| 西和县| 芜湖县| 江津市| 孝义市| 临沂市| 芦山县| 湖口县| 涟源市| 黄龙县| 通渭县| 铁岭县| 扎赉特旗| 临邑县| 咸宁市| 溆浦县| 太康县| 宜都市| 长宁区| 元氏县| 阳泉市| 特克斯县| 河北区| 勐海县| 四川省| 琼结县| 连云港市| 广灵县| 南漳县| 黄龙县| 巴林右旗| 宁乡县| 武威市|