1. 程式人生 > 其它 >SpringBoot 優雅整合Swagger Api 自動生成文件

SpringBoot 優雅整合Swagger Api 自動生成文件

原子類

阿里巴巴2021版JDK原始碼筆記(2月第三版).pdf

連結:https://pan.baidu.com/s/1XhVcfbGTpU83snOZVu8AXg
提取碼:l3gy

concurrent包的結構層次:Atomic類 -> 鎖與條件 -> 同步工具 -> 併發容器 -> 程式設計池 -> CpmpletableFuture

1. AtomicInteger和AtomicLong

1.1 原始碼及其原理

public final int addAndGet(int delta) {
    return unsafe.getAndAddInt(this, valueOffset, delta) + delta;
}
public final int getAndAddInt(Object var1, long var2, int var4) {
    int var5;
    do {
        var5 = this.getIntVolatile(var1, var2);
    } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

    return var5;
}

檢視原始碼可知,這裡主要是通過CAS(compareAndSwap)實現

1.2 悲觀鎖和樂觀鎖

  • 悲觀鎖:悲觀,開發人員認為資料發生併發衝突的概率很大,在讀操作之前就上鎖(正常都是讀後寫)

  • 樂關鎖:樂觀,開發人員認為資料發生併發衝突的概率比較小,讀操作不上鎖,寫操作才上鎖,判斷資料在此期間是否被其 他執行緒修改了,CAS就是典型的樂觀鎖模式(判斷資料是否被修改,同時寫回新值,這兩個操作要合成一個原子操作,也就是CAS)

1.3 Unsafe的CAS詳解

unsafe.compareAndSwapInt(this, valueOffset, expect, update);

1.4 自旋和阻塞

  • 自旋: 當程式拿不到鎖時,不放棄CPU,空轉,不斷重試
  • 阻塞:當程式拿不到鎖時,放棄CPU,進入阻塞狀態,等待後續被喚醒

就單核CPU而言,不能使用自旋,自旋會造成其他執行緒無法使用,對多核CPU而言,自旋可以減少執行緒切換的開銷,節省資源

自旋和阻塞可以結合使用,先嚐試自旋,若自旋幾次還不能拿到鎖,那就阻塞

2. AtomicBoolean和AtomicReference

在Unsafe類中,只提供了三種類型的CAS操作:int、long、Object(也就是引用型別)

2.1 如何實現 boolean

boolean通過int型別轉換

3. AtomicStampedReference和 AtomicMarkableReference

3.1 ABA 問題及解決方案

  • 問題:如果另外一個線 程把變數的值從A改為B,再從B改回到A,那麼儘管修改過兩次,可是 在當前執行緒做CAS操作的時候,卻會因為值沒變而認為資料沒有被其他執行緒修改過

  • 解決方案:要解決 ABA 問題,不僅要比較“值”,還要比較“版本號”

     public boolean compareAndSet(V   expectedReference,
                                     V   newReference,
                                     int expectedStamp,
                                     int newStamp) ;
    

3.2 為什麼沒有AtomicStampedInteger或 AtomictStampedLong

要同時比較資料的“值”和“版本號”,而Integer型或 者Long型的CAS沒有辦法同時比較兩個變數,於是只能把值和版本號封裝成一個物件,也就是這裡面的 Pair 內部類,然後通過物件引用的CAS來實現。

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

3.3 AtomicMarkableReference

AtomicMarkableReference與AtomicStampedReference原理類似, 只是Pair裡面的版本號是boolean型別的,而不是整型的累加變數

3.4 為什麼需要AtomicXXXFieldUpdater

如果一個類是自己編寫的,則可以在編寫的時候把成員變數定義 為Atomic型別。但如果是一個已經有的類,在不能更改其原始碼的情 況下,要想實現對其成員變數的原子操作,就需要AtomicIntegerFieldUpdater、AtomicLongFieldUpdater 和 AtomicReferenceFieldUpdater。

  • 限制: 從程式碼層次可以看出,欄位必須是volatile修飾,且不能是包裝類

    if (field.getType() != int.class)
        throw new IllegalArgumentException("Must be integer type");
    
    if (!Modifier.isVolatile(modifiers))
        throw new IllegalArgumentException("Must be volatile type");
    

4. AtomicIntegerArray、AtomicLongArray和 Atomic-ReferenceArray

陣列元素的原子操作,並不是說 對整個陣列的操作是原子的,而是針對陣列中一個元素的原子操作而言

 return unsafe.compareAndSwapInt(array, offset, expect, update);

5. Striped64與LongAdder

從JDK 8開始,針對Long型的原子操作,Java又提供了LongAdder、LongAccumulator;針對Double型別,Java提供了DoubleAdder、DoubleAccumulator

5. 1 LongAdder原理

AtomicLong內部是一個volatile long型變數,由多個執行緒對這個 變數進行CAS操作,多個執行緒同時對一個變數進行CAS操作,在高併發的場景下仍不夠快。把一個變數拆成多份,變為多個變數,有些類似於 ConcurrentHashMap 的分段鎖的例子,把一個Long型拆成一個base變 量外加多個Cell,每個Cell包裝了一個Long型變數當多個執行緒併發 累加的時候,如果併發度低,就直接加到base變數上;如果併發度 高,衝突大,平攤到這些Cell上。在最後取值的時候,再把base和這些Cell求sum運算

由於無論是long,還是double,都是64位的。但因為沒有double型的CAS操作,所以是通過把double型轉化成long型來實現的。所以, 上面的base和cell[]變數,是位於基類Striped64當中的。英文Striped意為“條帶”,也就是分片。

5.2 最終一致性

在sum求和函式中,並沒有對cells[]陣列加鎖。也就是說,一邊 有執行緒對其執行求和操作,一邊還有執行緒修改數組裡的值,也就是最 終一致性,而不是強一致性。這也類似於ConcurrentHashMap 中的 clear()函式,一邊執行清空操作,一邊還有執行緒放入資料,clear()函式呼叫完畢後再讀取,hash map裡面可能還有元素。因此,在LongAdder開篇的註釋中,把它和AtomicLong 的使用場景做了比較。它 適合高併發的統計場景,而不適合要對某個 Long 型變數進行嚴格同步的場景

5.3 偽共享與快取行填充

快取 與主記憶體進行資料交換的基本單位叫Cache Line(快取行)。在64位x86架構中,快取行是64位元組,也就是8個Long型的大小。這也意味著當快取失效,要重新整理到主記憶體的時候,最少要重新整理64位元組

主記憶體中有變數X、Y、Z(假設每個變數都是一個Long型),被CPU1和CPU2分別讀入自己的快取,放在了同一行Cache Line裡面。當CPU1修改了X變數,它要失效整行Cache Line,也就是往總 線上發訊息,通知CPU 2對應的Cache Line失效。由於Cache Line是資料交換的基本單位,無法只失效X,要失效就會失效整行的Cache Line,這會導致Y、Z變數的快取也失效。

Y、Z和X變數處在了同一行Cache Line裡面。要解 決這個問題,需要用到所謂的“快取行填充”,分別在X、Y、Z後面加 上7個無用的Long型,填充整個快取行,讓X、Y、Z處在三行不同的快取行中