在Java併發編程中,咱們常常使用鎖對競爭資源予以併發控制,以解決資源競爭的問題。但不管是使用 Lock 仍是 Synchronized,隨着鎖機制的引入,就不可避免的帶來另外一個問題,也就鎖與解鎖時的上下文切換,線程等待 等性能問題。如今回過頭來看,在有些場景中,是否真的須要引入鎖才能解決競爭資源共享問題?答案是否認的,在JDK源碼中,也爲咱們實現了。就是今天要介紹的另一種無鎖方案-CAS,它大量應用於JUC 包中,也是atomic包中各種的底層原理,其重要行可想而知。java
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 方法的源碼,以下所述:跨域
1 2 |
public final boolean compareAndSet(int expect, int update) { return unsafe.compareAndSwapInt(this, valueOffset, expect, update)} |
方法入參中 expect 爲指望值, update 爲待更新值。多線程
繼續往下看,compareAndSet方法內部使用到的是Unsafe.compareAndSwapInt()方法,以下所述:併發
1 |
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5); |
方法入參有四個,其中:cors
在Unsafe類中,同類的方法有如下幾個:性能
1 2 3 |
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,因爲篇幅緣由,就再也不展開說明,下次放單獨一篇文章中談談。this
在 CAS 中有一個特別經典的問題,也就是ABA。它說的是:內存值 Value 與指望值 A 進行比較前,Value已經發生過變化了,只不過是其變化後的值也爲Value。從而形成從結果上看,其結果一致是一致的,(多發生於多線程條件下)固然這也是符合CAS 條件的。在大多數場景下,咱們並不須要關心這種場景,在須要關心時,咱們也能夠使用JDK爲咱們提供了實現類 - AtomicStampedReference。在 AtomicStampedReference 類中,引入了標記位的概念,用於標記value值是否被修改過。結合value值 + 標記位是否一致,來判斷value值是否修改過。
其源碼以下:atom
1 2 3 4 5 6 7 8 9 10 11 12 |
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 爲 對象引用與版本標記對象,其源碼以下:
1 2 3 4 5 6 7 8 9 10 11 |
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 的,而是一種互補的關係。日常都在寫業務代碼,沒有更深層次的查看源碼,當查看源碼時,卻又是一件趣事,蠻好的!
相關閱讀:
1.《CORS跨域實踐》
2.《說說面試那些事》
3.《一個Java小細節!》
4.《記一個有趣的Java OOM!》
掃碼關注,一塊兒進步
我的博客: http://www.andyqian.com