您好,登錄后才能下訂單哦!
本文首發于個人公眾號《andyqian》,期待你的關注!
在Java并發編程中,我們經常使用鎖對競爭資源予以并發控制,以解決資源競爭的問題。但無論是使用 Lock 還是 Synchronized,隨著鎖機制的引入,就不可避免的帶來另一個問題,也就鎖與解鎖時的上下文切換,線程等待 等性能問題。現在回過頭來看,在有些場景中,是否真的需要引入鎖才能解決競爭資源共享問題?答案是否定的,在JDK源碼中,也為我們實現了。就是今天要介紹的另外一種無鎖方案-CAS,它大量應用于JUC 包中,也是atomic包中各類的底層原理,其重要行可想而知。
CAS 全稱為:Compare And Swap (比較與替換),其核心思想是:將內存值 Value 與期望值 A 進行比較,如果兩者相等,則將其設置為新值 B,否則不進行任何操作。CAS操作非常高效,在我看來,其原因有二,其一:底層調用的是 sun.misc.Unsafe 類,操作的是內存值,非常高效。其二:在多線程環境下,始終只有一個線程獲得執行權,未獲得執行權的線程并不會掛起而造成阻塞,而是以操作CAS失敗后再次執行CAS操作,直至成功,這一個過程,在Java中稱之為 “自旋”。
Java 中 CAS 應用的十分廣泛,幕后英雄是sun.misc.Unsafe 類,單獨看Unsafe類的CAS操作可能有些茫然,以我們熟悉的 AtomicInteger 類中的 compareAndSet 方法為引子,再分析到 Unsafe類可能會更好些。下面為AtomicInteger 類中 compareAndSet 方法的源碼,如下所述:
public?final?boolean?compareAndSet(int?expect,?int?update)?{ ????return?unsafe.compareAndSwapInt(this,?valueOffset,?expect,?update)}
方法入參中 expect 為期望值,update 為待更新值。繼續往下看,compareAndSet 方法內部使用到的是Unsafe.compareAndSwapInt()方法,如下所述:
public?final?native?boolean?compareAndSwapInt(Object?var1,?long?var2,?int?var4,?int?var5);
方法入參有四個,其中:
Object var1 為對象。
long var2 為 var1 對象的內存地址。
int var4 為 內存地址 中的期望值。
var5 為 待更新的值。
在Unsafe類中,同類的方法有以下幾個:
public?final?native?boolean?compareAndSwapObject(Object?var1,?long?var2,?Object?var4,?Object?var5); public?final?native?boolean?compareAndSwapLong(Object?var1,?long?var2,?long?var4,?long?var6);
其實 Unsafe 類還給我們提供了一系列底層的API,由于篇幅原因,就不再展開說明,下次放單獨一篇文章中談談。
在 CAS 中有一個特別經典的問題,也就是ABA。它說的是:內存值 Value 與期望值 A 進行比較前,Value已經發生過變化了,只不過是其變化后的值也為Value。從而造成從結果上看,其結果一致是一致的,(多發生于多線程條件下)當然這也是符合CAS 條件的。在大多數場景下,我們并不需要關心這種場景,在需要關心時,我們也可以使用JDK為我們提供了實現類 - AtomicStampedReference。在AomicStampedReference 類中,引入了標記位的概念,用于標記value值是否被修改過。結合value值 + 標記位是否一致,來判斷value值是否修改過。其源碼如下:
public?boolean?compareAndSet(V??expectedReference,?//?期望引用對象 ?????????????????????????????????V?newReference,????//?新的引用對象 ?????????????????????????????????int?expectedStamp,?//期望標志位 ?????????????????????????????????int?newStamp)??//?新的標識位 ????????Pair<V>?current?=?pair;??//?獲取對象與標識引用對 ????????return ????????????expectedReference?==?current.reference?&&???//?期望對象引用是否等于當前引用對象?(是否發生變化) ????????????expectedStamp?==?current.stamp&&??????//?期望stamp?是否等于當前stamp ????????????((newReference?==?current.reference?&&???//新的引用對象是否等于當前引用對象,新的stamp是否等于當前stamp ??????????????newStamp?==?current.stamp)?|| ?????????????casPair(current,?Pair.of(newReference,?newStamp)));??//進行pair?的cas操作 ????}
其中 pair 為 對象引用與版本標記對象,其源碼如下:
private?static?class?Pair<T>?{ ????????final?T?reference; ????????final?int?stamp; ????????private?Pair(T?reference,?int?stamp)?{ ????????????this.reference?=?reference; ????????????this.stamp?=?stamp; ????????} ????????static?<T>?Pair<T>?of(T?reference,?int?stamp)?{ ????????????return?new?Pair<T>(reference,?stamp); ????????} ????}
在Java 中 CAS 應用的十分廣泛,包括但不限于:atomic,synchorized 底層原理等等。但需要明確的是 CAS 的存在并不是用來替換 lock 的,而是一種互補的關系。平常都在寫業務代碼,沒有更深層次的查看源碼,當查看源碼時,卻又是一件趣事,蠻好的!
相關閱讀:
《談談 996 背后的現象》
《重構不完全指南!》
《說說Java 位運算》
《上千行存儲過程有感!》
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。