Java 基本功 之 CAS

本文首發於我的公衆號《andyqian》, 期待你的關注!

前言

  在Java併發編程中,咱們常常使用鎖對競爭資源予以併發控制,以解決資源競爭的問題。但不管是使用 Lock 仍是 Synchronized,隨着鎖機制的引入,就不可避免的帶來另外一個問題,也就鎖與解鎖時的上下文切換,線程等待 等性能問題。如今回過頭來看,在有些場景中,是否真的須要引入鎖才能解決競爭資源共享問題?答案是否認的,在JDK源碼中,也爲咱們實現了。就是今天要介紹的另一種無鎖方案-CAS,它大量應用於JUC 包中,也是atomic包中各種的底層原理,其重要行可想而知。java

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

 

方法入參有四個,其中:cors

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

ABA 問題

   在 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

相關文章
相關標籤/搜索