(201)Atomic*實(shí)現(xiàn)原理

成神之路,需要耐得住寂寞,開(kāi)啟總結(jié)源碼之旅。

我閱讀總結(jié)源碼的目的不是為了炫技,我希望通過(guò)閱讀源碼可以解決一些問(wèn)題,也可以通過(guò)閱讀源碼理解別人思想,以幫助我們更好的寫(xiě)我們的代碼。

引子

在多線程的場(chǎng)景中,我們需要如何同步數(shù)據(jù),通常會(huì)使用synchronized或者lock來(lái)處理,使用了synchronized意味著內(nèi)核態(tài)的一次切換。這是一個(gè)很重的操作。有沒(méi)有一種方式,可以比較便利的實(shí)現(xiàn)一些簡(jiǎn)單的數(shù)據(jù)同步,比如計(jì)數(shù)器等等。concurrent包下的atomic提供我們這么一種輕量級(jí)的數(shù)據(jù)同步的選擇。

他山之石

使用例子

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

請(qǐng)暫時(shí)先忽略CountDownLatch,只是為了在主線程中等待所有子線程執(zhí)行完,打印結(jié)果。這個(gè)結(jié)果永遠(yuǎn)都是100。如果將AtomicInteger換成Integer,打印結(jié)果基本都是小于100。

原理

我們可以看一下AtomicInteger的代碼


image.png

他的值是存在一個(gè)volatile的int里面。volatile只能保證這個(gè)變量的可見(jiàn)性。不能保證他的原子性。

可以看看getAndIncrement這個(gè)類(lèi)似i++的函數(shù),可以發(fā)現(xiàn),是調(diào)用了UnSafe中的getAndAddInt。


image.png

UnSafe是何方神圣?可以參考上面的文章了解一下,UnSafe提供了java可以直接操作底層的能力。
進(jìn)一步,我們可以發(fā)現(xiàn)實(shí)現(xiàn)方式:


image.png

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

優(yōu)缺點(diǎn)

CAS相對(duì)于其他鎖,不會(huì)進(jìn)行內(nèi)核態(tài)操作,有著一些性能的提升。但同時(shí)引入自旋,當(dāng)鎖競(jìng)爭(zhēng)較大的時(shí)候,自旋次數(shù)會(huì)增多。cpu資源會(huì)消耗很高。

換句話說(shuō),CAS+自旋適合使用在低并發(fā)有同步數(shù)據(jù)的應(yīng)用場(chǎng)景。

jdk8做出的改進(jìn)和努力

在jdk8中引入了4個(gè)新的計(jì)數(shù)器類(lèi)型,LongAdder、LongAccumulator、DoubleAdder、DoubleAccumulator。他們都是繼承于Striped64。

在LongAdder 與AtomicLong有什么區(qū)別?
Atomic*遇到的問(wèn)題是,只能運(yùn)用于低并發(fā)場(chǎng)景。因此LongAddr在這基礎(chǔ)上引入了分段鎖的概念??梢詤⒖肌禞DK8系列之LongAdder解析》一起看看做了什么。

大概就是當(dāng)競(jìng)爭(zhēng)不激烈的時(shí)候,所有線程都是通過(guò)CAS對(duì)同一個(gè)變量(Base)進(jìn)行修改,當(dāng)競(jìng)爭(zhēng)激烈的時(shí)候,會(huì)將根據(jù)當(dāng)前線程哈希到對(duì)于Cell上進(jìn)行修改(多段鎖)。


image.png

可以看到大概實(shí)現(xiàn)原理是:通過(guò)CAS樂(lè)觀鎖保證原子性,通過(guò)自旋保證當(dāng)次修改的最終修改成功,通過(guò)降低鎖粒度(多段鎖)增加并發(fā)性能。

關(guān)鍵點(diǎn)

  • 自旋
  • CAS樂(lè)觀鎖
  • 多段鎖(分治思想)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容