1. 程式人生 > >面試:為了進阿里,又把併發CAS(Compare and Swap)實現重新精讀一遍

面試:為了進阿里,又把併發CAS(Compare and Swap)實現重新精讀一遍

該系列文章已收錄在公眾號【Ccww技術部落格】,原創技術文章第一時間推出

前言

在面試中,併發執行緒安全提問必然是不會缺少的,那基礎的CAS原理也必須瞭解,這樣在面試中才能加分,那來看看面試可能會問那些問題:

  • 什麼是樂觀鎖與悲觀鎖

  • 什麼樂觀鎖的實現方式-CAS(Compare and Swap),CAS(Compare and Swap)實現原理

  • 在JDK併發包中的使用

  • CAS的缺陷

 


1. 什麼是樂觀鎖與悲觀鎖?

悲觀鎖

總是假設最壞的情況,每次讀取資料的時候都預設其他執行緒會更改資料,因此需要進行加鎖操作,當其他執行緒想要訪問資料時,都需要阻塞掛起。悲觀鎖的實現:

  • 傳統的關係型資料庫使用這種鎖機制,比如行鎖,表鎖等,讀鎖,寫鎖等,都是在做操作之前先上鎖;

  • Java裡面的同步synchronized關鍵字的實現。

樂觀鎖

樂觀鎖,其實就是一種思想,總是認為不會產生併發問題,每次讀取資料的時候都認為其他執行緒不會修改資料,所以不上鎖,但是在更新的時候會判斷一下在此期間別的執行緒有沒有修改過資料,樂觀鎖適用於讀操作多的場景,這樣可以提高程式的吞吐量。實現方式:

  • CAS實現:Java中java.util.concurrent.atomic包下面的原子變數使用了樂觀鎖的一種CAS實現方式,CAS分析看下節。

  • 版本號控制:一般是在資料表中加上一個資料版本號version欄位,表示資料被修改的次數,當資料被修改時,version值會加一。當執行緒A要更新資料值時,在讀取資料的同時也會讀取version值,在提交更新時,若剛才讀取到的version值為當前資料庫中的version值相等時才更新,否則重試更新操作,直到更新成功

樂觀鎖適用於讀多寫少的情況下(多讀場景),悲觀鎖比較適用於寫多讀少場景

 


2. 樂觀鎖的實現方式-CAS(Compare and Swap),CAS(Compare and Swap)實現原理

背景

在jdk1.5之前都是使用synchronized關鍵字保證同步,synchronized保證了無論哪個執行緒持有共享變數的鎖,都會採用獨佔的方式來訪問這些變數,導致會存在這些問題:

  • 在多執行緒競爭下,加鎖、釋放鎖會導致較多的上下文切換和排程延時,引起效能問題

  • 如果一個執行緒持有鎖,其他的執行緒就都會掛起,等待持有鎖的執行緒釋放鎖。

  • 如果一個優先順序高的執行緒等待一個優先順序低的執行緒釋放鎖,會導致優先順序倒置,引起效能風險

為了優化悲觀鎖這些問題,就出現了樂觀鎖:

假設沒有併發衝突,每次不加鎖操作同一變數,如果有併發衝突導致失敗,則重試直至成功。

CAS(Compare and Swap)原理

CAS 全稱是 compare and swap(比較並且交換),是一種用於在多執行緒環境下實現同步功能的機制,其也是無鎖優化,或者叫自旋,還有自適應自旋。

在jdk中,CASvolatile關鍵字作為實現併發包的基石。沒有CAS就不會有併發包,java.util.concurrent中藉助了CAS指令實現了一種區別於synchronized的一種樂觀鎖。

 

樂觀鎖的一種典型實現機制(CAS):

樂觀鎖主要就是兩個步驟:

  • 衝突檢測

  • 資料更新

當多個執行緒嘗試使用CAS同時更新同一個變數時,只有一個執行緒可以更新變數的值,其他的執行緒都會失敗,失敗的執行緒並不會掛起,而是告知這次競爭中失敗了,並可以再次嘗試。

在不使用鎖的情況下保證執行緒安全,CAS實現機制中有重要的三個運算元:

  • 需要讀寫的記憶體位置(V)

  • 預期原值(A)

  • 新值(B)

首先先讀取需要讀寫的記憶體位置(V),然後比較需要讀寫的記憶體位置(V)和預期原值(A),如果記憶體位置與預期原值的A相匹配,那麼將記憶體位置的值更新為新值B。如果記憶體位置與預期原值的值不匹配,那麼處理器不會做任何操作。無論哪種情況,它都會在 CAS 指令之前返回該位置的值。具體可以分成三個步驟:

  • 讀取(需要讀寫的記憶體位置(V))

  • 比較(需要讀寫的記憶體位置(V)和預期原值(A))

  • 寫回(新值(B))


3. CAS在JDK併發包中的使用

在JDK1.5以上 java.util.concurrent(JUC java併發工具包)是基於CAS演算法實現的,相比於synchronized獨佔鎖,堵塞演算法,CAS是非堵塞演算法的一種常見實現,使用樂觀鎖JUC在效能上有了很大的提升。

 

CAS如何在不使用鎖的情況下保證執行緒安全,看併發包中的原子操作類AtomicInteger::getAndIncrement()方法(相當於i++的操作):

// AtomicInteger中
//value的偏移量
private static final long valueOffset; 
//獲取值
private volatile int value;
//設定value的偏移量
static {
        try {
            valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }
//增加1
public final int getAndIncrement() {
        return unsafe.getAndAddInt(this, valueOffset, 1);
    }
​

  

  • 首先value必須使用了volatile修飾,這就保證了他的可見性與有序性

  • 需要初始化value的偏移量

  • unsafe.getAndAddInt通過偏移量進行CAS操作,每次從記憶體中讀取資料然後將資料進行+1操作,然後對原資料,+1後的結果進行CAS操作,成功的話返回結果,否則重試直到成功為止。

    //unsafe中
    public final int getAndAddInt(Object var1, long var2, int var4) {
        int var5;
        do {
            //使用偏移量獲取記憶體中value值
            var5 = this.getIntVolatile(var1, var2);
           //比較並value加+1
        } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
        return var5;
    }
    

      

     

JAVA實現CAS的原理,unsafe::compareAndSwapInt是藉助C來呼叫CPU底層指令實現的。下面是sun.misc.Unsafe::compareAndSwapInt()方法的原始碼:

public final native boolean compareAndSwapInt(Object o, long offset,
                                               int expected, int x);

  

4. CAS的缺陷

ABA問題

在多執行緒場景下CAS會出現ABA問題,例如有2個執行緒同時對同一個值(初始值為A)進行CAS操作,這三個執行緒如下

執行緒1,期望值為A,欲更新的值為B

執行緒2,期望值為A,欲更新的值為B

執行緒3,期望值為B,欲更新的值為A

  • 執行緒1搶先獲得CPU時間片,而執行緒2因為其他原因阻塞了,執行緒1取值與期望的A值比較,發現相等然後將值更新為B,

  • 這個時候出現了執行緒3,執行緒3取值與期望的值B比較,發現相等則將值更新為A

  • 此時執行緒2從阻塞中恢復,並且獲得了CPU時間片,這時候執行緒2取值與期望的值A比較,發現相等則將值更新為B,雖然執行緒2也完成了操作,但是執行緒2並不知道值已經經過了A->B->A的變化過程。

ABA問題帶來的危害:

小明在提款機,提取了50元,因為提款機問題,有兩個執行緒,同時把餘額從100變為50

執行緒1(提款機):獲取當前值100,期望更新為50,

執行緒2(提款機):獲取當前值100,期望更新為50,

執行緒1成功執行,執行緒2某種原因block了,這時,某人給小明匯款50 執行緒3(預設):獲取當前值50,期望更新為100,

這時候執行緒3成功執行,餘額變為100, 執行緒2從Block中恢復,獲取到的也是100,compare之後,繼續更新餘額為50!!!

此時可以看到,實際餘額應該為100(100-50+50),但是實際上變為了50(100-50+50-50)這就是ABA問題帶來的成功提交。

 

解決方法

  • AtomicStampedReference: 帶有時間戳的物件引用來解決ABA問題。這個類的compareAndSet方法作用是首先檢查當前引用是否等於預期引用,並且當前標誌是否等於預期標誌,如果全部相等,則以原子方式將該引用和該標誌的值設定為給定的更新值。

  • public boolean compareAndSet(
                   V      expectedReference,//預期引用
                   V      newReference,//更新後的引用
                  int    expectedStamp, //預期標誌
                  int    newStamp //更新後的標誌
    ​
    ) 
  • version:在變數前面加上版本號,每次變數更新的時候變數的版本號都+1,即A->B->A就變成了1A->2B->3A

迴圈時間長開銷大

自旋CAS(不成功,就一直迴圈執行,直到成功)如果長時間不成功,會給CPU帶來極大的執行開銷。

解決方法:

  • 限制自旋次數,防止進入死迴圈

  • JVM能支援處理器提供的pause指令那麼效率會有一定的提升,

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

當對一個共享變數執行操作時,我們可以使用迴圈CAS的方式來保證原子操作,但是對多個共享變數操作時,迴圈CAS就無法保證操作的原子性

解決方法:

  • 如果需要對多個共享變數進行操作,可以使用加鎖方式(悲觀鎖)保證原子性,

  • 可以把多個共享變數合併成一個共享變數進行CAS操作。

該系列文章已收錄在公眾號【Ccww技術部落格】,原創技術文章第一時間推出

 

相關推薦

面試為了阿里併發CASCompare and Swap實現重新精讀

該系列文章已收錄在公眾號【Ccww技術部落格】,原創技術文章第一時間推出 前言 在面試中,併發執行緒安全提問必然是不會缺少的,那基礎的CAS原理也必須瞭解,這樣在面試中才能加分,那來看看面試可能會問那些問題: 什麼是樂觀鎖與悲觀鎖 什麼樂觀鎖的實現方式-CAS(Compare and Swap),

面試為了阿里死磕了ThreadLocal記憶體洩露原因

前言 在分析ThreadLocal導致的記憶體洩露前,需要普及瞭解一下記憶體洩露、強引用與弱引用以及GC回收機制,這樣才能更好的分析為什麼ThreadLocal會導致記憶體洩露呢?更重要的是知道該如何避免這樣情況發生,增強系統的健壯性。 記憶體洩露 記憶體洩露為程式在申請記憶體後,無法釋放已申請的記憶體空間,

面試為了阿里重新翻閱了Volatile與Synchronized

該系列文章收錄在公眾號【Ccww技術部落格】,原創技術文章早於部落格推出 在深入理解使用Volatile與Synchronized時,應該先理解明白Java記憶體模型 (Java Memory Model,JMM) Java記憶體模型(Java Memory Model,JMM) Java記憶體(JMM

例項建立一個表格分頁顯示資料MongoDB資料庫儲存功能實現增刪改查

需求:建立一個表格,分頁顯示資料,功能:實現增刪改查 效果圖: 自動建立一個專案 命令列: express mongodb-demo --view=ejs cd mongodb-demo npm install npm install mongodb --save npm sta

獵豹傅盛升維思考降維攻擊!深度好文

      轉載地址:http://www.woshipm.com/it/218149.html         前不久,我讀完《三體》,幾乎幫我建立了一個更高維度的世界觀和科學觀。因為你突然意識到,這個世界不

手把手實現微信網頁授權和微信支付附源代碼VUE and thinkPHP

nec ble 名單 ret 一次 hash 掃一掃 網頁 ada wechat github 手把手實現微信網頁授權和微信支付,附源代碼(VUE and thinkPHP) 概述 公眾號開發是痛苦的,痛苦在好多問題開發者文檔是沒有提到的,是需要你猜的. 在開發過程中翻

程式設計師canvas寫雨滴特效成功面試阿里月薪20K

  程式設計師javascript/canvas雨滴特效:      今日逛部落格看到一位大牛程式設計師共享了他做的一個用JavaScript和canvas做成的雨滴特效,自個也copy了一份,送給頭條號上想操練自個水平的同學可以去操練下,這位大牛程式設計師拿著自個JavaScript和ca

程式設計師吐槽放棄公務員阿里如今擔心中年危機

沒有比較就沒有傷害,當一個人面對選擇時,走了一條自己當時嚮往並認為能實現夢想的路,時過境遷,後來發現並不是自己計劃的那樣,生活也過得不如意。這時候發現走另一條路的人生活過得比自己好多了,恍然發現自己曾經的選擇似乎是個錯誤,這時候就會感嘆選擇大於努力。 就有一名程式設

真實面試經歷十面阿里七面頭條六個Offer

面試者背景簡介   雙非末流一本,大三,CS(電腦科學)專業,有百度實習經歷   02   面試情況     十面阿里,總共分為阿里雲四面,螞蟻兩面,菜鳥四面;   七面頭條分為金融三面,抖音一面

程式設計師想跳槽阿里怕自己過去因學歷低而被冷落該不該去呢?

現在說起國內的網際網路企業,大家首先想到的便是阿里、騰訊這種大型企業,這些企業也是很多畢業生所向往的公司,其中吸引人的不僅僅是薪資待遇,另外阿里的大廠光環哪怕你想要跳槽也是個好簡歷,但是這種大廠也不是一般人能夠進的,就有一位大專學歷的p7,想進去但是又怕自己學歷不夠。 原貼如下:

面試解決重點問題計算兩個時間段是否有交集的演算法及其應用例項

1、通過 if 判斷語句進行判斷,if(endTime1 > startTime2 && endTime2 > startTime1) 那麼這兩個時間段有交集,一個時間段的結束時間大於另一個時間段的開始時間,如果成立那麼兩個時間段有交集。

一個阿里架構師十年的從業總結比起掉髮我更怕掉隊文末福利分享

驀然回首,從畢業到現在做後臺開發已經十年了,這十年中我獲得了很多,技術能力、培訓、出國、大公司的經歷,還有很多志同道合的朋友。但再仔細一想,這十年碼農路上我至少浪費了五年時間,這五年可以足夠讓自己成長為一個優秀的程式設計師,可惜我錯過了,我用這五年時間和很多程式設計師一樣在困

阿里程式設計師吐槽同樣是阿里北京到點下班杭州12點燈火通明

在大廠上班哪有輕鬆可言,加班是常態,甚至通宵都很正常,即使如此但也有不少人擠破頭想去大廠,畢竟薪酬豐厚,相對小廠來說大廠各方面更加穩定,即便加班很多,但為了高薪也能忍受。 在網際網路職場吐槽論壇,一名阿里員工怨聲載道,表達了各方面的不滿,其這樣吐槽到:千萬別來杭州,加班之都,一個個都不要命

入門Java阿里分享我學Java的那些思考!

也許今天的你在公司做著 CRUD 的工作,也許你還在為面試造火箭、工作擰螺絲悻悻不已,也許掙扎了許久卻找不到進步的方向。   不是計算機相關專業的我,從開始學習 Java 到進入阿里,用了一年多點的時間,很多人覺得是進步比較快的,問我怎麼學習 Java 才能快速進步,微信一一回復

【Java開發者專場】阿里特邀專家徐雷Java為王網際網路高併發架構設計選型之路

本篇文章來自於2018年12月22日舉辦的《阿里雲棲開發者沙龍—Java技術專場》,徐雷專家是該專場第三位演講的嘉賓,本篇文章是根據徐雷專家在《阿里雲棲開發者沙龍—Java技術專場》的演講視訊以及PPT整理而成。 摘要:Java從誕生以來幾乎一直是排名第一的語言,長期霸榜。在架構師成長道路中,學習Ja

在Docker中執行Java為了防止失敗你需要知道這些

摘要: 很多開發者會(或者應該)知道,當我們為執行在Linux容器(Docker、rkt、runC、lxcfs等)中的Java程式去設定JVM的GC、堆大小和執行時編譯器的引數時並沒有得到預想的效果。當我們通過“java -jar mypplication-fat.jar

稻盛和夫真正的聰明人善於事物簡單化

稻盛先生說:     我們往往有一種傾向,就是將事物考慮得過於複雜。但是,事物的本質其實極為單純。乍看很複雜的事物,不過是若干簡單事物的組合。人類的遺傳基因,由多達30億個鹽基排列構成,但是表達基因的密碼種類僅有4個。     真理之布由一根紗線

matlab讀取mp4檔案逐幀儲存成圖片檔案檔名的編號隨幀數變化

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %=====ABSTRACT===== % translate MP4 file to images. cause the appearance of block arti

秋招打怪升級之路十面阿里終獲offer!

開源專案推薦: JavaGuide: Java學習+面試指南!Github 56k+ 的 Java專案。一份涵蓋大部分Java程式設計師所需要掌握的核心知識。 springboot-guide:SpringBoot 學習指南!重要知識點以及常見面試題總結。 programmer-advancement:

KafkaZK+Kafka+Spark Streaming集群環境搭建二十五Structured Streaming同一個topic中包含組數據的多個部分按照key它們拼接為條記錄以及遇到的問題

eas array 記錄 splay span ack timestamp b- each 需求: 目前kafka的topic上有一批數據,這些數據被分配到9個不同的partition中(就是發布時key:{m1,m2,m3,m4...m9},value:{records