(201)Atomic*實現原理

林灣村龍貓發表於2018-09-16

成神之路,需要耐得住寂寞,開啟總結原始碼之旅。

我閱讀總結原始碼的目的不是為了炫技,我希望通過閱讀原始碼可以解決一些問題,也可以通過閱讀原始碼理解別人思想,以幫助我們更好的寫我們的程式碼。

引子

在多執行緒的場景中,我們需要如何同步資料,通常會使用synchronized或者lock來處理,使用了synchronized意味著核心態的一次切換。這是一個很重的操作。有沒有一種方式,可以比較便利的實現一些簡單的資料同步,比如計數器等等。concurrent包下的atomic提供我們這麼一種輕量級的資料同步的選擇。

他山之石

使用例子

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;

public class App {

    public static void main(String[] args) throws Exception {
        CountDownLatch countDownLatch = new CountDownLatch(100);

        AtomicInteger atomicInteger = new AtomicInteger(0);
        for (int i = 0; i < 100; i++) {
            new Thread() {
                @Override
                public void run() {
                    atomicInteger.getAndIncrement();

                    countDownLatch.countDown();
                }
            }.start();
        }

        countDownLatch.await();

        System.out.println(atomicInteger.get());
    }
}
複製程式碼

請暫時先忽略CountDownLatch,只是為了在主執行緒中等待所有子執行緒執行完,列印結果。這個結果永遠都是100。如果將AtomicInteger換成Integer,列印結果基本都是小於100。

原理

我們可以看一下AtomicInteger的程式碼

image.png
他的值是存在一個volatile的int裡面。volatile只能保證這個變數的可見性。不能保證他的原子性。

可以看看getAndIncrement這個類似i++的函式,可以發現,是呼叫了UnSafe中的getAndAddInt。

image.png

UnSafe是何方神聖?可以參考上面的文章瞭解一下,UnSafe提供了java可以直接操作底層的能力。 進一步,我們可以發現實現方式:

image.png

如何保證原子性:自旋 + CAS(樂觀鎖)。在這個過程中,通過compareAndSwapInt比較更新value值,如果更新失敗,重新獲取舊值,然後更新。

優缺點

CAS相對於其他鎖,不會進行核心態操作,有著一些效能的提升。但同時引入自旋,當鎖競爭較大的時候,自旋次數會增多。cpu資源會消耗很高。

換句話說,CAS+自旋適合使用在低併發有同步資料的應用場景。

jdk8做出的改進和努力

在jdk8中引入了4個新的計數器型別,LongAdder、LongAccumulator、DoubleAdder、DoubleAccumulator。他們都是繼承於Striped64。

在LongAdder 與AtomicLong有什麼區別? Atomic*遇到的問題是,只能運用於低併發場景。因此LongAddr在這基礎上引入了分段鎖的概念。可以參考《JDK8系列之LongAdder解析》一起看看做了什麼。

大概就是當競爭不激烈的時候,所有執行緒都是通過CAS對同一個變數(Base)進行修改,當競爭激烈的時候,會將根據當前執行緒雜湊到對於Cell上進行修改(多段鎖)。

image.png

可以看到大概實現原理是:通過CAS樂觀鎖保證原子性,通過自旋保證當次修改的最終修改成功,通過**降低鎖粒度(多段鎖)**增加併發效能。

關鍵點

  • 自旋
  • CAS樂觀鎖
  • 多段鎖(分治思想)

相關文章