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

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

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

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

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

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

一、什么是樂觀鎖與悲觀鎖?

樂觀鎖和悲觀鎖是一種概念和思想:RYp28資訊網(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è)釋放鎖,他拿到鎖后才可以訪問。傳統(tǒng)的數(shù)據(jù)庫(kù)里面就用到了這種鎖機(jī)制,例如:行鎖,表鎖,讀鎖,寫鎖,都是在操作前先上鎖。java中的synchronized的實(shí)現(xiàn)也是一種悲觀鎖。RYp28資訊網(wǎng)——每日最新資訊28at.com

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

二、背景:

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

  • 在多線程競(jìng)爭(zhēng)下,加鎖、釋放鎖會(huì)導(dǎo)致比較多的上下文切換和調(diào)度延時(shí),引起性能問題。
  • 一個(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)存中來保持內(nèi)存的可見性。通過 CPU內(nèi)存屏障禁止編譯器指令性重排來保證并發(fā)操作的有序性RYp28資訊網(wǎng)——每日最新資訊28at.com

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

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

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

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

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

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

三、CAS原理:

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

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

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

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

對(duì)
java.util.concurrent.atomic 包下的原子類 AtomicInteger 中的 compareAndSet 方法進(jìn)行分析,相關(guān)分析如下:
RYp28資訊網(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);          // ......  }

上面的分析看起來比較多,不過主流程并不復(fù)雜。如果不糾結(jié)于代碼細(xì)節(jié),還是比較容易看懂的。接下來,我會(huì)分析 Windows 平臺(tái)下的 Atomic::cmpxchg 函數(shù)。繼續(xù)往下看吧。RYp28資訊網(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)容。如下:RYp28資訊網(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)記處,     * 也就是越過 _emit 0xF0 指令,直接執(zhí)行 cmpxchg 指令。也就是不在下面的 cmpxchg 指令     * 前加 lock 前綴。     */    je L0    /*     * 0xF0 是 lock 前綴的機(jī)器碼,這里沒有使用 lock,而是直接使用了機(jī)器碼的形式。至于這樣做的     * 原因可以參考知乎的一個(gè)回答:     *     https://www.zhihu.com/question/50878124/answer/123099923     */     _emit 0xF0L0:    /*     * 比較并交換。簡(jiǎn)單解釋一下下面這條指令,熟悉匯編的朋友可以略過下面的解釋:     *   cmpxchg: 即“比較并交換”指令     *   dword: 全稱是 double word,在 x86/x64 體系中,一個(gè)      *          word = 2 byte,dword = 4 byte = 32 bit     *   ptr: 全稱是 pointer,與前面的 dword 連起來使用,表明訪問的內(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)過程就講完了,CAS 的實(shí)現(xiàn)離不開處理器的支持。如上面源代碼所示,程序會(huì)根據(jù)當(dāng)前處理器的類型來決定是否為 cmpxchg 指令添加 lock 前綴。如果程序是在多處理器上運(yùn)行,就為 cmpxchg 指令加上 lock 前綴(lock cmpxchg)。反之,如果程序是在單處理器上運(yùn)行,就省略 lock 前綴(單處理器自身會(huì)維護(hù)單處理器內(nèi)的順序一致性,不需要 lock 前綴提供的內(nèi)存屏障效果)。RYp28資訊網(wǎng)——每日最新資訊28at.com

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

  • 確保對(duì)內(nèi)存的讀 - 改 - 寫操作原子執(zhí)行。在 Pentium 及 Pentium 之前的處理器中,帶有 lock 前綴的指令在執(zhí)行期間會(huì)鎖住總線,使得其他處理器暫時(shí)無(wú)法通過總線訪問內(nèi)存。很顯然,這會(huì)帶來昂貴的開銷。從 Pentium 4,Intel Xeon 及 P6 處理器開始,intel 在原有總線鎖的基礎(chǔ)上做了一個(gè)很有意義的優(yōu)化:如果要訪問的內(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ú)法讀 / 寫該指令要訪問的內(nèi)存區(qū)域,因此能保證指令執(zhí)行的原子性。這個(gè)操作過程叫做緩存鎖定(cache locking),緩存鎖定將大大降低 lock 前綴指令的執(zhí)行開銷,但是當(dāng)多處理器之間的競(jìng)爭(zhēng)程度很高或者指令訪問的內(nèi)存地址未對(duì)齊時(shí),仍然會(huì)鎖住總線。
  • 禁止該指令與之前和之后的讀和寫指令重排序。
  • 把寫緩沖區(qū)中的所有數(shù)據(jù)刷新到內(nèi)存中。

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

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

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

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

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

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

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

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

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

在單線程的情況下,一般有兩種分配策略:RYp28資訊網(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í)的安全問題也有兩種策略:RYp28資訊網(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,可以通過-XX:+/-UseTLAB參數(shù)來進(jìn)行配置(jdk5及以后的版本默認(rèn)是啟用TLAB的)。RYp28資訊網(wǎng)——每日最新資訊28at.com

四、CAS存在的問題:

4.1 ABA 問題

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

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

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

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

從Java1.5開始JDK的atomic包里提供了一個(gè)類AtomicStampedReference來解決ABA問題。這個(gè)類的compareAndSet方法作用是首先檢查當(dāng)前引用是否等于預(yù)期引用,并且當(dāng)前標(biāo)志是否等于預(yù)期標(biāo)志,如果全部相等,則以原子方式將該引用和該標(biāo)志的值設(shè)置為給定的更新值。RYp28資訊網(wǎng)——每日最新資訊28at.com

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

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

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

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

CAS 與 Synchronized 的使用情景:   RYp28資訊網(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。RYp28資訊網(wǎng)——每日最新資訊28at.com

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

聲明:本網(wǎng)頁(yè)內(nèi)容旨在傳播知識(shí),若有侵權(quá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)
  • 8月總票房已突破10億!《封神》第一:口碑已經(jīng)成了

    8月5日消息,據(jù)燈塔專業(yè)版數(shù)據(jù),截至8月5日9時(shí)35分,8月總票房(含預(yù)售)已突破10億。其中,《封神》以大比分的優(yōu)勢(shì)領(lǐng)先。根據(jù)官方消息,目前該片總票房已經(jīng)超過14.
  • 線程通訊的三種方法!通俗易懂

    線程通信是指多個(gè)線程之間通過某種機(jī)制進(jìn)行協(xié)調(diào)和交互,例如,線程等待和通知機(jī)制就是線程通訊的主要手段之一。 在 Java 中,線程等待和通知的實(shí)現(xiàn)手段有以下幾種方式:Object 類下
  • SpringBoot中使用Cache提升接口性能詳解

    環(huán)境:springboot2.3.12.RELEASE + JSR107 + Ehcache + JPASpring 框架從 3.1 開始,對(duì) Spring 應(yīng)用程序提供了透明式添加緩存的支持。和事務(wù)支持一樣,抽象緩存允許一致地使用各
  • 使用AIGC工具提升安全工作效率

    在日常工作中,安全人員可能會(huì)涉及各種各樣的安全任務(wù),包括但不限于:開發(fā)某些安全工具的插件,滿足自己特定的安全需求;自定義github搜索工具,快速查找所需的安全資料、漏洞poc、exp
  • 慕巖炮轟抖音,百合網(wǎng)今何在?

    來源:價(jià)值研究所 作者:Hernanderz&ldquo;難道就因?yàn)樽约旱囊粋€(gè)產(chǎn)品牛逼了,從客服到總裁,都不愿意正視自己產(chǎn)品和運(yùn)營(yíng)上的問題,選擇逃避了嗎?&rdquo;這一番話,出自百合網(wǎng)聯(lián)合創(chuàng)
  • 騰訊VS網(wǎng)易,最卷游戲暑期檔,誰(shuí)能笑到最后?

    作者:無(wú)銹缽來源:財(cái)經(jīng)無(wú)忌7月16日晚,上海1862時(shí)尚藝術(shù)中心。伴隨著幻象的精準(zhǔn)命中,碩大的熒幕之上,比分被定格在了14:12,被寄予厚望的EDG戰(zhàn)隊(duì)以絕對(duì)的優(yōu)勢(shì)戰(zhàn)勝了BLG戰(zhàn)隊(duì),拿下了總決
  • 華為Mate60標(biāo)準(zhǔn)版細(xì)節(jié)曝光:經(jīng)典星環(huán)相機(jī)模組回歸

    這段時(shí)間以來,關(guān)于華為新旗艦的爆料日漸密集。據(jù)此前多方爆料,今年華為將開始恢復(fù)一年雙旗艦戰(zhàn)略,除上半年推出的P60系列外,往年下半年的Mate系列也將
  • 由于成本持續(xù)增加,筆記本產(chǎn)品價(jià)格預(yù)計(jì)將明顯上漲

    根據(jù)知情人士透露,由于材料、物流等成本持續(xù)增加,筆記本產(chǎn)品價(jià)格預(yù)計(jì)將在2021年下半年有明顯上漲。進(jìn)入6月下旬以來,全球半導(dǎo)體芯片缺貨情況加劇,顯卡、處理器
  • 北京:科技教育體驗(yàn)基地開始登記

      北京“科技館之城”科技教育體驗(yàn)基地登記和認(rèn)證工作日前啟動(dòng)。首批北京科技教育體驗(yàn)基地?cái)M于2023年全國(guó)科普日期間掛牌,后續(xù)還將開展常態(tài)化登記?! ”本┛萍冀逃w驗(yàn)基
Top