AVt天堂网 手机版,亚洲va久久久噜噜噜久久4399,天天综合亚洲色在线精品,亚洲一级Av无码毛片久久精品

當(dāng)前位置:首頁(yè) > 科技  > 軟件

并發(fā)樂(lè)觀鎖CAS原理,吊打問(wèn)并發(fā)的面試官

來(lái)源: 責(zé)編: 時(shí)間:2023-10-13 14:34:42 229觀看
導(dǎo)讀CAS的英文為Compare and Swap 翻譯為比較并交換。CAS加volatile關(guān)鍵字是實(shí)現(xiàn)并發(fā)包的基石。沒(méi)有CAS就不會(huì)有并發(fā)包,synchronized是一種獨(dú)占鎖、悲觀鎖,java.util.concurrent中借助了CAS指令實(shí)現(xiàn)了一種區(qū)別于synchronize

CAS的英文為Compare and Swap 翻譯為比較并交換。EXR28資訊網(wǎng)——每日最新資訊28at.com

CASvolatile關(guān)鍵字是實(shí)現(xiàn)并發(fā)包的基石。沒(méi)有CAS就不會(huì)有并發(fā)包,synchronized是一種獨(dú)占鎖、悲觀鎖,java.util.concurrent中借助了CAS指令實(shí)現(xiàn)了一種區(qū)別于synchronized的一種樂(lè)觀鎖。EXR28資訊網(wǎng)——每日最新資訊28at.com

一、什么是樂(lè)觀鎖與悲觀鎖?

樂(lè)觀鎖和悲觀鎖是一種概念和思想:EXR28資訊網(wǎng)——每日最新資訊28at.com

悲觀鎖:總是假設(shè)最壞的情況,每次去拿數(shù)據(jù)的時(shí)候都認(rèn)為別人會(huì)修改,所以每次在拿數(shù)據(jù)的時(shí)候都會(huì)上鎖,這樣當(dāng)?shù)诙€(gè)線程想拿這個(gè)數(shù)據(jù)的時(shí)候,第二個(gè)線程會(huì)一直堵塞,直到第一個(gè)釋放鎖,他拿到鎖后才可以訪問(wèn)。傳統(tǒng)的數(shù)據(jù)庫(kù)里面就用到了這種鎖機(jī)制,例如:行鎖,表鎖,讀鎖,寫(xiě)鎖,都是在操作前先上鎖。java中的synchronized的實(shí)現(xiàn)也是一種悲觀鎖。EXR28資訊網(wǎng)——每日最新資訊28at.com

樂(lè)觀鎖:樂(lè)觀鎖概念為,每次拿數(shù)據(jù)的時(shí)候都認(rèn)為別的線程不會(huì)修改這個(gè)數(shù)據(jù),所以不會(huì)上鎖,但是在更新的時(shí)候會(huì)判斷一下在此期間別的線程有沒(méi)有修改過(guò)數(shù)據(jù),樂(lè)觀鎖適用于讀操作多的場(chǎng)景,這樣可以提高程序的吞吐量。在Java中
java.util.concurrent.atomic包下面的原子變量就是使用了樂(lè)觀鎖的一種實(shí)現(xiàn)方式CAS實(shí)現(xiàn)。
EXR28資訊網(wǎng)——每日最新資訊28at.com

二、背景:

在JDK 5之前Java語(yǔ)言是靠 synchronized 關(guān)鍵字保證同步的,這會(huì)導(dǎo)致有鎖。鎖機(jī)制存在以下問(wèn)題:EXR28資訊網(wǎng)——每日最新資訊28at.com

  • 在多線程競(jìng)爭(zhēng)下,加鎖、釋放鎖會(huì)導(dǎo)致比較多的上下文切換和調(diào)度延時(shí),引起性能問(wèn)題。
  • 一個(gè)線程持有鎖會(huì)導(dǎo)致其它所有需要此鎖的線程掛起。
  • 如果一個(gè)優(yōu)先級(jí)高的線程等待一個(gè)優(yōu)先級(jí)低的線程釋放鎖會(huì)導(dǎo)致優(yōu)先級(jí)倒置,引起性能風(fēng)險(xiǎn)。

Volatile關(guān)鍵字能夠在并發(fā)條件下,強(qiáng)制將修改后的值刷新到主內(nèi)存中來(lái)保持內(nèi)存的可見(jiàn)性。通過(guò) CPU內(nèi)存屏障禁止編譯器指令性重排來(lái)保證并發(fā)操作的有序性EXR28資訊網(wǎng)——每日最新資訊28at.com

如果多個(gè)線程同時(shí)操作 Volatile 修飾的變量,也會(huì)造成數(shù)據(jù)的不一致。EXR28資訊網(wǎng)——每日最新資訊28at.com

public class Test {    public volatile int inc = 0;         public void increase() {        inc++;    }         public static void main(String[] args) {        final Test test = new Test();        for(int i=0;i<10;i++){            new Thread(){                public void run() {                    for(int j=0;j<1000;j++)                        test.increase();                };            }.start();        }            while(Thread.activeCount()>1)            Thread.yield();        System.out.println(test.inc);    }}

事實(shí)上運(yùn)行它會(huì)發(fā)現(xiàn)每次運(yùn)行結(jié)果都不一致,都是一個(gè)小于10000的數(shù)字。EXR28資訊網(wǎng)——每日最新資訊28at.com

假如某個(gè)時(shí)刻變量 inc 的值為10:EXR28資訊網(wǎng)——每日最新資訊28at.com

  • 線程1對(duì)變量進(jìn)行自增操作,線程1先讀取了變量inc的原始值,然后線程1被阻塞了;
  • 然后線程2對(duì)變量進(jìn)行自增操作,線程2也去讀取變量inc的原始值,由于線程1只是對(duì)變量inc進(jìn)行讀取操作,而沒(méi)有對(duì)變量進(jìn)行修改操作,所以不會(huì)導(dǎo)致線程2的工作內(nèi)存中緩存變量inc的緩存行無(wú)效,所以線程2會(huì)直接去主存讀取inc的值,發(fā)現(xiàn)inc的值時(shí)10,然后進(jìn)行加1操作,并把11寫(xiě)入工作內(nèi)存,最后寫(xiě)入主存。
  • 然后線程1接著進(jìn)行加1操作,由于已經(jīng)讀取了inc的值,注意此時(shí)在線程1的工作內(nèi)存中inc的值仍然為10,所以線程1對(duì)inc進(jìn)行加1操作后inc的值為11,然后將11寫(xiě)入工作內(nèi)存,最后寫(xiě)入主存。
  • 那么兩個(gè)線程分別進(jìn)行了一次自增操作后,inc只增加了1。

之所以出現(xiàn)還是 volatile 只是保證讀寫(xiě)具有原子性,但是對(duì)于 ++ 操作的復(fù)合操作是不存在原子操作的。只能在有限的一些情形下使用 volatile 變量替代鎖。要使 volatile 變量提供理想的線程安全,比如:對(duì)變量的寫(xiě)操作不依賴于當(dāng)前值。EXR28資訊網(wǎng)——每日最新資訊28at.com

volatile 是不錯(cuò)的機(jī)制,但是 volatile 不能保證原子性。因此對(duì)于同步最終還是要回到鎖機(jī)制上來(lái)。EXR28資訊網(wǎng)——每日最新資訊28at.com

獨(dú)占鎖是一種悲觀鎖,synchronized 就是一種獨(dú)占鎖,會(huì)導(dǎo)致其它所有需要鎖的線程掛起,等待持有鎖的線程釋放鎖。而另一個(gè)更加有效的鎖就是樂(lè)觀鎖。所謂樂(lè)觀鎖就是,每次不加鎖而是假設(shè)沒(méi)有沖突而去完成某項(xiàng)操作,如果因?yàn)闆_突失敗就重試,直到成功為止。樂(lè)觀鎖用到的機(jī)制就是 CAS。EXR28資訊網(wǎng)——每日最新資訊28at.com

三、CAS原理:

CAS 操作包含三個(gè)操作數(shù) -- 內(nèi)存位置、預(yù)期數(shù)值和新值。CAS 的實(shí)現(xiàn)邏輯是將內(nèi)存位置處的數(shù)值與預(yù)期數(shù)值想比較,若相等,則將內(nèi)存位置處的值替換為新值。若不相等,則不做任何操作。EXR28資訊網(wǎng)——每日最新資訊28at.com

在 Java 中,Java 并沒(méi)有直接實(shí)現(xiàn) CAS,CAS 相關(guān)的實(shí)現(xiàn)是通過(guò) C++ 內(nèi)聯(lián)匯編的形式實(shí)現(xiàn)的。Java 代碼需通過(guò) JNI 才能調(diào)用。EXR28資訊網(wǎng)——每日最新資訊28at.com

在JVM中的CAS操作就是基于處理器的CMPXCHG匯編指令實(shí)現(xiàn)的,因此,JVM中的CAS的原子性是處理器保障的EXR28資訊網(wǎng)——每日最新資訊28at.com

CAS 是一條 CPU 的原子指令(cmpxchg指令),不會(huì)造成所謂的數(shù)據(jù)不一致問(wèn)題,Unsafe 提供的 CAS 方法(如compareAndSwapXXX)底層實(shí)現(xiàn)即為 CPU 指令 cmpxchgEXR28資訊網(wǎng)——每日最新資訊28at.com

對(duì)
java.util.concurrent.atomic 包下的原子類 AtomicInteger 中的 compareAndSet 方法進(jìn)行分析,相關(guān)分析如下:
EXR28資訊網(wǎng)——每日最新資訊28at.com

public class AtomicInteger extends Number implements java.io.Serializable {        // setup to use Unsafe.compareAndSwapInt for updates    private static final Unsafe unsafe = Unsafe.getUnsafe();        private static final long valueOffset;       static {               try {                        // 計(jì)算變量 value 在類對(duì)象中的偏移            valueOffset = unsafe.objectFieldOffset                (AtomicInteger.class.getDeclaredField("value"));        } catch (Exception ex) { throw new Error(ex); }    }        private volatile int value;        public final boolean compareAndSet(int expect, int update) {                /**         * compareAndSet 實(shí)際上只是一個(gè)殼子,主要的邏輯封裝在 Unsafe 的          * compareAndSwapInt 方法中         */        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);    }        // ......}  public final class Unsafe {          // compareAndSwapInt 是 native 類型的方法,繼續(xù)往下看      public final native boolean compareAndSwapInt(Object o, long offset,  int expected,  int x);          // ......  }
public class AtomicInteger extends Number implements java.io.Serializable {        // setup to use Unsafe.compareAndSwapInt for updates    private static final Unsafe unsafe = Unsafe.getUnsafe();        private static final long valueOffset;       static {               try {                        // 計(jì)算變量 value 在類對(duì)象中的偏移            valueOffset = unsafe.objectFieldOffset                (AtomicInteger.class.getDeclaredField("value"));        } catch (Exception ex) { throw new Error(ex); }    }        private volatile int value;        public final boolean compareAndSet(int expect, int update) {                /**         * compareAndSet 實(shí)際上只是一個(gè)殼子,主要的邏輯封裝在 Unsafe 的          * compareAndSwapInt 方法中         */        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);    }        // ......}  public final class Unsafe {          // compareAndSwapInt 是 native 類型的方法,繼續(xù)往下看      public final native boolean compareAndSwapInt(Object o, long offset,  int expected,  int x);          // ......  }

上面的分析看起來(lái)比較多,不過(guò)主流程并不復(fù)雜。如果不糾結(jié)于代碼細(xì)節(jié),還是比較容易看懂的。接下來(lái),我會(huì)分析 Windows 平臺(tái)下的 Atomic::cmpxchg 函數(shù)。繼續(xù)往下看吧。EXR28資訊網(wǎng)——每日最新資訊28at.com

// atomic_windows_x86.inline.hpp#define LOCK_IF_MP(mp) __asm cmp mp, 0  /                       __asm je L0      /                       __asm _emit 0xF0 /                       __asm L0:              inline jint Atomic::cmpxchg (jint exchange_value, volatile jint* dest, jint compare_value) {  // alternative for InterlockedCompareExchange  int mp = os::is_MP();  __asm {    mov edx, dest    mov ecx, exchange_value    mov eax, compare_value    LOCK_IF_MP(mp)    cmpxchg dword ptr [edx], ecx  }}

上面的代碼由 LOCK_IF_MP 預(yù)編譯標(biāo)識(shí)符和 cmpxchg 函數(shù)組成。為了看到更清楚一些,我們將 cmpxchg 函數(shù)中的 LOCK_IF_MP 替換為實(shí)際內(nèi)容。如下:EXR28資訊網(wǎng)——每日最新資訊28at.com

inline jint Atomic::cmpxchg (jint exchange_value, volatile jint* dest, jint compare_value) {    // 判斷是否是多核 CPU  int mp = os::is_MP();  __asm {    // 將參數(shù)值放入寄存器中    mov edx, dest    // 注意: dest 是指針類型,這里是把內(nèi)存地址存入 edx 寄存器中    mov ecx, exchange_value    mov eax, compare_value        // LOCK_IF_MP    cmp mp, 0    /*     * 如果 mp = 0,表明是線程運(yùn)行在單核 CPU 環(huán)境下。此時(shí) je 會(huì)跳轉(zhuǎn)到 L0 標(biāo)記處,     * 也就是越過(guò) _emit 0xF0 指令,直接執(zhí)行 cmpxchg 指令。也就是不在下面的 cmpxchg 指令     * 前加 lock 前綴。     */    je L0    /*     * 0xF0 是 lock 前綴的機(jī)器碼,這里沒(méi)有使用 lock,而是直接使用了機(jī)器碼的形式。至于這樣做的     * 原因可以參考知乎的一個(gè)回答:     *     https://www.zhihu.com/question/50878124/answer/123099923     */     _emit 0xF0L0:    /*     * 比較并交換。簡(jiǎn)單解釋一下下面這條指令,熟悉匯編的朋友可以略過(guò)下面的解釋:     *   cmpxchg: 即“比較并交換”指令     *   dword: 全稱是 double word,在 x86/x64 體系中,一個(gè)      *          word = 2 byte,dword = 4 byte = 32 bit     *   ptr: 全稱是 pointer,與前面的 dword 連起來(lái)使用,表明訪問(wèn)的內(nèi)存單元是一個(gè)雙字單元     *   [edx]: [...] 表示一個(gè)內(nèi)存單元,edx 是寄存器,dest 指針值存放在 edx 中。     *          那么 [edx] 表示內(nèi)存地址為 dest 的內(nèi)存單元     *               * 這一條指令的意思就是,將 eax 寄存器中的值(compare_value)與 [edx] 雙字內(nèi)存單元中的值     * 進(jìn)行對(duì)比,如果相同,則將 ecx 寄存器中的值(exchange_value)存入 [edx] 內(nèi)存單元中。     */    cmpxchg dword ptr [edx], ecx  }}

到這里 CAS 的實(shí)現(xiàn)過(guò)程就講完了,CAS 的實(shí)現(xiàn)離不開(kāi)處理器的支持。如上面源代碼所示,程序會(huì)根據(jù)當(dāng)前處理器的類型來(lái)決定是否為 cmpxchg 指令添加 lock 前綴。如果程序是在多處理器上運(yùn)行,就為 cmpxchg 指令加上 lock 前綴(lock cmpxchg)。反之,如果程序是在單處理器上運(yùn)行,就省略 lock 前綴(單處理器自身會(huì)維護(hù)單處理器內(nèi)的順序一致性,不需要 lock 前綴提供的內(nèi)存屏障效果)。EXR28資訊網(wǎng)——每日最新資訊28at.com

intel 的手冊(cè)對(duì) lock 前綴的說(shuō)明如下:EXR28資訊網(wǎng)——每日最新資訊28at.com

  • 確保對(duì)內(nèi)存的讀 - 改 - 寫(xiě)操作原子執(zhí)行。在 Pentium 及 Pentium 之前的處理器中,帶有 lock 前綴的指令在執(zhí)行期間會(huì)鎖住總線,使得其他處理器暫時(shí)無(wú)法通過(guò)總線訪問(wèn)內(nèi)存。很顯然,這會(huì)帶來(lái)昂貴的開(kāi)銷。從 Pentium 4,Intel Xeon 及 P6 處理器開(kāi)始,intel 在原有總線鎖的基礎(chǔ)上做了一個(gè)很有意義的優(yōu)化:如果要訪問(wèn)的內(nèi)存區(qū)域(area of memory)在 lock 前綴指令執(zhí)行期間已經(jīng)在處理器內(nèi)部的緩存中被鎖定(即包含該內(nèi)存區(qū)域的緩存行當(dāng)前處于獨(dú)占或以修改狀態(tài)),并且該內(nèi)存區(qū)域被完全包含在單個(gè)緩存行(cache line)中,那么處理器將直接執(zhí)行該指令。由于在指令執(zhí)行期間該緩存行會(huì)一直被鎖定,其它處理器無(wú)法讀 / 寫(xiě)該指令要訪問(wèn)的內(nèi)存區(qū)域,因此能保證指令執(zhí)行的原子性。這個(gè)操作過(guò)程叫做緩存鎖定(cache locking),緩存鎖定將大大降低 lock 前綴指令的執(zhí)行開(kāi)銷,但是當(dāng)多處理器之間的競(jìng)爭(zhēng)程度很高或者指令訪問(wèn)的內(nèi)存地址未對(duì)齊時(shí),仍然會(huì)鎖住總線。
  • 禁止該指令與之前和之后的讀和寫(xiě)指令重排序。
  • 把寫(xiě)緩沖區(qū)中的所有數(shù)據(jù)刷新到內(nèi)存中。

上面的第 2 點(diǎn)和第 3 點(diǎn)所具有的內(nèi)存屏障效果,足以同時(shí)實(shí)現(xiàn) volatile 讀和 volatile 寫(xiě)的內(nèi)存語(yǔ)義。EXR28資訊網(wǎng)——每日最新資訊28at.com

經(jīng)過(guò)上面的這些分析,現(xiàn)在我們終于能明白為什么 JDK 文檔說(shuō) CAS 同時(shí)具有 volatile 讀和 volatile 寫(xiě)的內(nèi)存語(yǔ)義了。EXR28資訊網(wǎng)——每日最新資訊28at.com

Java 的 CAS 會(huì)使用現(xiàn)代處理器上提供的高效機(jī)器級(jí)別原子指令,這些原子指令以原子方式對(duì)內(nèi)存執(zhí)行讀 - 改 - 寫(xiě)操作,這是在多處理器中實(shí)現(xiàn)同步的關(guān)鍵(從本質(zhì)上來(lái)說(shuō),能夠支持原子性讀 - 改 - 寫(xiě)指令的計(jì)算機(jī)器,是順序計(jì)算圖靈機(jī)的異步等價(jià)機(jī)器,因此任何現(xiàn)代的多處理器都會(huì)去支持某種能對(duì)內(nèi)存執(zhí)行原子性讀 - 改 - 寫(xiě)操作的原子指令)。同時(shí),volatile 變量的讀 / 寫(xiě)和 CAS 可以實(shí)現(xiàn)線程之間的通信。把這些特性整合在一起,就形成了整個(gè) concurrent 包得以實(shí)現(xiàn)的基石。如果我們仔細(xì)分析 concurrent 包的源代碼實(shí)現(xiàn),會(huì)發(fā)現(xiàn)一個(gè)通用化的實(shí)現(xiàn)模式:EXR28資訊網(wǎng)——每日最新資訊28at.com

  • 首先,聲明共享變量為 volatile;
  • 然后,使用 CAS 的原子條件更新來(lái)實(shí)現(xiàn)線程之間的同步;
  • 同時(shí),配合以 volatile 的讀 / 寫(xiě)和 CAS 所具有的 volatile 讀和寫(xiě)的內(nèi)存語(yǔ)義來(lái)實(shí)現(xiàn)線程之間的通信。

AQS,非阻塞數(shù)據(jù)結(jié)構(gòu)和原子變量類(
java.util.concurrent.atomic 包中的類),這些 concurrent 包中的基礎(chǔ)類都是使用這種模式來(lái)實(shí)現(xiàn)的,而 concurrent 包中的高層類又是依賴于這些基礎(chǔ)類來(lái)實(shí)現(xiàn)的。從整體來(lái)看,concurrent 包的實(shí)現(xiàn)示意圖如下:
EXR28資訊網(wǎng)——每日最新資訊28at.com

EXR28資訊網(wǎng)——每日最新資訊28at.com

JVM中的CAS(堆中對(duì)象的分配): EXR28資訊網(wǎng)——每日最新資訊28at.com

Java 調(diào)用 new object() 會(huì)創(chuàng)建一個(gè)對(duì)象,這個(gè)對(duì)象會(huì)被分配到 JVM 的堆中。那么這個(gè)對(duì)象到底是怎么在堆中保存的呢?EXR28資訊網(wǎng)——每日最新資訊28at.com

首先,new object() 執(zhí)行的時(shí)候,這個(gè)對(duì)象需要多大的空間,其實(shí)是已經(jīng)確定的,因?yàn)?java 中的各種數(shù)據(jù)類型,占用多大的空間都是固定的(對(duì)其原理不清楚的請(qǐng)自行Google)。那么接下來(lái)的工作就是在堆中找出那么一塊空間用于存放這個(gè)對(duì)象。EXR28資訊網(wǎng)——每日最新資訊28at.com

在單線程的情況下,一般有兩種分配策略:EXR28資訊網(wǎng)——每日最新資訊28at.com

  1. 指針碰撞:這種一般適用于內(nèi)存是絕對(duì)規(guī)整的(內(nèi)存是否規(guī)整取決于內(nèi)存回收策略),分配空間的工作只是將指針像空閑內(nèi)存一側(cè)移動(dòng)對(duì)象大小的距離即可。
  2. 空閑列表:這種適用于內(nèi)存非規(guī)整的情況,這種情況下JVM會(huì)維護(hù)一個(gè)內(nèi)存列表,記錄哪些內(nèi)存區(qū)域是空閑的,大小是多少。給對(duì)象分配空間的時(shí)候去空閑列表里查詢到合適的區(qū)域然后進(jìn)行分配即可。

但是JVM不可能一直在單線程狀態(tài)下運(yùn)行,那樣效率太差了。由于再給一個(gè)對(duì)象分配內(nèi)存的時(shí)候不是原子性的操作,至少需要以下幾步:查找空閑列表、分配內(nèi)存、修改空閑列表等等,這是不安全的。解決并發(fā)時(shí)的安全問(wèn)題也有兩種策略:EXR28資訊網(wǎng)——每日最新資訊28at.com

  1. CAS:實(shí)際上虛擬機(jī)采用CAS配合上失敗重試的方式保證更新操作的原子性,原理和上面講的一樣。
  2. TLAB:如果使用CAS其實(shí)對(duì)性能還是會(huì)有影響的,所以 JVM 又提出了一種更高級(jí)的優(yōu)化策略:每個(gè)線程在 Java 堆中預(yù)先分配一小塊內(nèi)存,稱為本地線程分配緩沖區(qū)(TLAB),線程內(nèi)部需要分配內(nèi)存時(shí)直接在 TLAB 上分配就行,避免了線程沖突。只有當(dāng)緩沖區(qū)的內(nèi)存用光需要重新分配內(nèi)存的時(shí)候才會(huì)進(jìn)行CAS操作分配更大的內(nèi)存空間。

虛擬機(jī)是否使用TLAB,可以通過(guò)-XX:+/-UseTLAB參數(shù)來(lái)進(jìn)行配置(jdk5及以后的版本默認(rèn)是啟用TLAB的)。EXR28資訊網(wǎng)——每日最新資訊28at.com

四、CAS存在的問(wèn)題:

4.1 ABA 問(wèn)題

談到 CAS,基本上都要談一下 CAS 的 ABA 問(wèn)題。CAS 由三個(gè)步驟組成,分別是“讀取-比較-寫(xiě)回”。考慮這樣一種情況,線程1和線程2同時(shí)執(zhí)行 CAS 邏輯,兩個(gè)線程的執(zhí)行順序如下:EXR28資訊網(wǎng)——每日最新資訊28at.com

  • 時(shí)刻1:線程1執(zhí)行讀取操作,獲取原值 A,然后線程被切換走
  • 時(shí)刻2:線程2執(zhí)行完成 CAS 操作將原值由 A 修改為 B
  • 時(shí)刻3:線程2再次執(zhí)行 CAS 操作,并將原值由 B 修改為 A
  • 時(shí)刻4:線程1恢復(fù)運(yùn)行,將比較值(compareValue)與原值(oldValue)進(jìn)行比較,發(fā)現(xiàn)兩個(gè)值相等。

然后用新值(newValue)寫(xiě)入內(nèi)存中,完成 CAS 操作EXR28資訊網(wǎng)——每日最新資訊28at.com

如上流程,線程1并不知道原值已經(jīng)被修改過(guò)了,在它看來(lái)并沒(méi)什么變化,所以它會(huì)繼續(xù)往下執(zhí)行流程。對(duì)于 ABA 問(wèn)題,通常的處理措施是對(duì)每一次 CAS 操作設(shè)置版本號(hào)。EXR28資訊網(wǎng)——每日最新資訊28at.com

ABA問(wèn)題的解決思路其實(shí)也很簡(jiǎn)單,就是使用版本號(hào)。在變量前面追加上版本號(hào),每次變量更新的時(shí)候把版本號(hào)加1,那么A→B→A就會(huì)變成1A→2B→3A了。EXR28資訊網(wǎng)——每日最新資訊28at.com

java.util.concurrent.atomic 包下提供了一個(gè)可處理 ABA 問(wèn)題的原子類 AtomicStampedReference,EXR28資訊網(wǎng)——每日最新資訊28at.com

從Java1.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è)置為給定的更新值。EXR28資訊網(wǎng)——每日最新資訊28at.com

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

自旋CAS(不成功,就一直循環(huán)執(zhí)行,直到成功) 如果長(zhǎng)時(shí)間不成功,會(huì)給 CPU 帶來(lái)非常大的執(zhí)行開(kāi)銷。如果JVM能支持處理器提供的 pause 指令那么效率會(huì)有一定的提升,pause指令有兩個(gè)作用,第一它可以延遲流水線執(zhí)行指令(de-pipeline),使 CPU 不會(huì)消耗過(guò)多的執(zhí)行資源,延遲的時(shí)間取決于具體實(shí)現(xiàn)的版本,在一些處理器上延遲時(shí)間是零。第二它可以避免在退出循環(huán)的時(shí)候因內(nèi)存順序沖突(memory order violation)而引起 CPU 流水線被清空(CPU pipeline flush),從而提高 CPU 的執(zhí)行效率。EXR28資訊網(wǎng)——每日最新資訊28at.com

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

當(dāng)對(duì)一個(gè)共享變量執(zhí)行操作時(shí),我們可以使用循環(huán) CAS 的方式來(lái)保證原子操作,但是對(duì)多個(gè)共享變量操作時(shí),循環(huán) CAS 就無(wú)法保證操作的原子性,這個(gè)時(shí)候就可以用鎖,或者有一個(gè)取巧的辦法,就是把多個(gè)共享變量合并成一個(gè)共享變量來(lái)操作。比如有兩個(gè)共享變量 i=2,j=a,合并一下 ij=2a,然后用CAS來(lái)操作ij。從Java1.5開(kāi)始JDK提供了 AtomicReference 類來(lái)保證引用對(duì)象之間的原子性,你可以把多個(gè)變量放在一個(gè)對(duì)象里來(lái)進(jìn)行 CAS 操作。EXR28資訊網(wǎng)——每日最新資訊28at.com

CAS 與 Synchronized 的使用情景:   EXR28資訊網(wǎng)——每日最新資訊28at.com

  1. 對(duì)于資源競(jìng)爭(zhēng)較少(線程沖突較輕)的情況,使用synchronized同步鎖進(jìn)行線程阻塞和喚醒切換以及用戶態(tài)內(nèi)核態(tài)間的切換操作額外浪費(fèi)消耗cpu資源;而CAS基于硬件實(shí)現(xiàn),不需要進(jìn)入內(nèi)核,不需要切換線程,操作自旋幾率較少,因此可以獲得更高的性能。
  2. 對(duì)于資源競(jìng)爭(zhēng)嚴(yán)重(線程沖突嚴(yán)重)的情況,CAS自旋的概率會(huì)比較大,從而浪費(fèi)更多的CPU資源,效率低于synchronized。

補(bǔ)充: synchronized 在 jdk1.6 之后,已經(jīng)改進(jìn)優(yōu)化。synchronized 的底層實(shí)現(xiàn)主要依靠 Lock-Free 的隊(duì)列,基本思路是自旋后阻塞,競(jìng)爭(zhēng)切換后繼續(xù)競(jìng)爭(zhēng)鎖,稍微犧牲了公平性,但獲得了高吞吐量。在線程沖突較少的情況下,可以獲得和 CAS 類似的性能;而線程沖突嚴(yán)重的情況下,性能遠(yuǎn)高于 CAS。EXR28資訊網(wǎng)——每日最新資訊28at.com

本文鏈接:http://www.tebozhan.com/showinfo-26-13497-0.html并發(fā)樂(lè)觀鎖CAS原理,吊打問(wèn)并發(fā)的面試官

聲明:本網(wǎng)頁(yè)內(nèi)容旨在傳播知識(shí),若有侵權(quán)等問(wèn)題請(qǐng)及時(shí)與本網(wǎng)聯(lián)系,我們將在第一時(shí)間刪除處理。郵件:2376512515@qq.com

上一篇: 深入了解桶排序:原理、性能分析與 Java 實(shí)現(xiàn)

下一篇: 必知必會(huì)的22種設(shè)計(jì)模式(GO語(yǔ)言)

標(biāo)簽:
  • 熱門焦點(diǎn)
Top