1. 程式人生 > >Java 基本功 之 CAS

Java 基本功 之 CAS

本文首發於個人公眾號《andyqian》, 期待你的關注!

前言

  在Java併發程式設計中,我們經常使用鎖對競爭資源予以併發控制,以解決資源競爭的問題。但無論是使用 Lock 還是 Synchronized,隨著鎖機制的引入,就不可避免的帶來另一個問題,也就鎖與解鎖時的上下文切換,執行緒等待 等效能問題。現在回過頭來看,在有些場景中,是否真的需要引入鎖才能解決競爭資源共享問題?答案是否定的,在JDK原始碼中,也為我們實現了。就是今天要介紹的另外一種無鎖方案-CAS,它大量應用於JUC 包中,也是atomic包中各類的底層原理,其重要行可想而知。

CAS 簡介

  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);

 

方法入參有四個,其中:

  1. Object var1 為物件。
  2. long var2 為 var1 物件的記憶體地址。
  3. int var4 為 記憶體地址 中的期望值。
  4. var5 為 待更新的值。

在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,由於篇幅原因,就不再展開說明,下次放單獨一篇文章中談談。

ABA 問題

   在 CAS 中有一個特別經典的問題,也就是ABA。它說的是:記憶體值 Value 與期望值 A 進行比較前,Value已經發生過變化了,只不過是其變化後的值也為Value。從而造成從結果上看,其結果一致是一致的,(多發生於多執行緒條件下)當然這也是符合CAS 條件的。在大多數場景下,我們並不需要關心這種場景,在需要關心時,我們也可以使用JDK為我們提供了實現類 - AtomicStampedReference。在 AtomicStampedReference 類中,引入了標記位的概念,用於標記value值是否被修改過。結合value值 + 標記位是否一致,來判斷value值是否修改過。
其原始碼如下:

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.c