【Java源碼計(jì)劃】LongAdder<rt.jar_java.util.concurrent.atomic.LongAdder>

LongAdder

源碼解讀

源碼解讀部分按照我得理解翻譯和解讀注解并添加相關(guān)的部分代碼解讀

保持一個(gè)或者多個(gè)變量,初始值設(shè)置為零用于求和。當(dāng)出現(xiàn)多個(gè)線程競(jìng)爭(zhēng)進(jìn)行一個(gè)數(shù)的更新時(shí),這個(gè)變量集合可以動(dòng)態(tài)的擴(kuò)展。最后當(dāng)需要求和的時(shí)候或者說(shuō)需要這個(gè)Long型的值時(shí),可以通過(guò)把當(dāng)前這些變量求和,合并后得出最終的和。

這個(gè)類在一些多線程環(huán)境下表現(xiàn)要比AtomicLong這個(gè)類好,比如多個(gè)線程同時(shí)更新一個(gè)求和的變量,比如統(tǒng)計(jì)集合的數(shù)量,但是不能用作細(xì)粒度的同步控制變量,換句話說(shuō)這個(gè)是可能有誤差的。在低并發(fā)競(jìng)爭(zhēng)的情況下LongAdder和AtomicLong的性能表現(xiàn)沒(méi)什么差別,但是當(dāng)高并發(fā)競(jìng)爭(zhēng)的時(shí)候,這個(gè)類將具備更好的吞吐性能,但是相應(yīng)的也會(huì)耗費(fèi)相當(dāng)?shù)目臻g。

LongAdder類可以和ConcurrentHashMap一起用一個(gè)可擴(kuò)展的增長(zhǎng)序列。例如一個(gè)統(tǒng)計(jì)一個(gè)柱狀圖或者統(tǒng)計(jì)變量的集合之類的功能,如果沒(méi)有初始化這個(gè)key對(duì)應(yīng)value可以通過(guò)jdk1.8在map接口中提供的函數(shù) ConcurrentHashMap.computeIfAbsent(k -> new LongAdder().increment());來(lái)實(shí)現(xiàn)對(duì)是否存在的判斷和加一的操作。

LongAdder繼承了Number抽象類,但是并沒(méi)有定義一些方法例如,equals,hashCode,compareTo,因?yàn)閷?shí)例的期望用途是進(jìn)行一些比較頻繁的變化,所以自然也就不需要用于作為集合的key。

public LongAdder()

默認(rèn)構(gòu)造方法會(huì)初始化一個(gè)新的adder并將和初始化為0.

public LongAdder() {
    //沒(méi)什么特殊的
}

public void add(long x)

將給定的值增加進(jìn)去。

   public void add(long x) {
        //此處涉及的Cell類與Striped64有關(guān)
        Cell[] as; 
        long b, v;
        int m;
        Cell a;
        //casBase方法來(lái)源于Striped64,放到對(duì)應(yīng)的地方細(xì)講
        //casBase會(huì)嘗試著進(jìn)行修改base的操作,如果成功,那就執(zhí)行結(jié)束
        //如果失敗意味著發(fā)生多線程競(jìng)爭(zhēng),會(huì)采取措施
        
        if ((as = cells) != null || !casBase(b = base, b + x)) {
            boolean uncontended = true;
            //通過(guò)哈希值來(lái)獲取當(dāng)前線程對(duì)應(yīng)的cells數(shù)組中的位置getProbe()
            //獲取該位置上的cell,如果該cell不為null,
            //那么就試圖將本次計(jì)數(shù)累計(jì)到該cell上,a.cas()
            //如果不成功,那么就需要Striped64類的longAccumulate方法來(lái)進(jìn)行計(jì)數(shù)累計(jì)
            //涉及的Cell類的方法和Striped64類中的方法會(huì)放到對(duì)應(yīng)的地方
            if (as == null || (m = as.length - 1) < 0 ||
                (a = as[getProbe() & m]) == null ||
                !(uncontended = a.cas(v = a.value, v + x)))
                longAccumulate(x, null, uncontended);
        }
    }

以下簡(jiǎn)要介紹一下Striped64

Striped64是在java8中添加用來(lái)支持累加器的并發(fā)組件,它可以在并發(fā)環(huán)境下使用來(lái)做某種計(jì)數(shù),Striped64的設(shè)計(jì)思路是在競(jìng)爭(zhēng)激烈的時(shí)候盡量分散競(jìng)爭(zhēng),在實(shí)現(xiàn)上,Striped64維護(hù)了一個(gè)base Count和一個(gè)Cell數(shù)組,計(jì)數(shù)線程會(huì)首先試圖更新base變量,如果成功則退出計(jì)數(shù),否則會(huì)認(rèn)為當(dāng)前競(jìng)爭(zhēng)是很激烈的,那么就會(huì)通過(guò)Cell數(shù)組來(lái)分散計(jì)數(shù),Striped64根據(jù)線程來(lái)計(jì)算哈希,然后將不同的線程分散到不同的Cell數(shù)組的index上,然后這個(gè)線程的計(jì)數(shù)內(nèi)容就會(huì)保存在該Cell的位置上面,基于這種設(shè)計(jì),最后的總計(jì)數(shù)需要結(jié)合base以及散落在Cell數(shù)組中的計(jì)數(shù)內(nèi)容。

類似的設(shè)計(jì)思想——java7的ConcurrentHashMap實(shí)現(xiàn),也就是所謂的分段鎖算法,ConcurrentHashMap會(huì)將記錄根據(jù)key的hashCode來(lái)分散到不同的segment上,線程想要操作某個(gè)記錄只需要鎖住這個(gè)記錄對(duì)應(yīng)著的segment就可以了,而其他segment并不會(huì)被鎖住,其他線程任然可以去操作其他的segment,這樣就顯著提高了并發(fā)度。

但是注意,雖然這個(gè)思想很棒,但是java8中的ConcurrentHashMap實(shí)現(xiàn)已經(jīng)拋棄了java7中分段鎖的設(shè)計(jì),而采用更為輕量級(jí)的CAS來(lái)協(xié)調(diào)并發(fā)。

LongAdder就是基于Striped64實(shí)現(xiàn)的計(jì)數(shù)器

Striped64的詳細(xì)內(nèi)容我們會(huì)放到Striped64的源碼中說(shuō)

public void increment()

方法等價(jià)于調(diào)用add(1L)方法

    public void increment() {
        //等價(jià)于調(diào)用了add方法
        add(1L);
    }

public void decrement()

方法等價(jià)于調(diào)用add(-1L)方法

    public void decrement() {
        //等價(jià)于調(diào)用了add方法
        add(-1L);
    }

public long sum()

返回當(dāng)前值,注意返回的數(shù)值并不是符合原子性的值快照,在沒(méi)有并發(fā)更新的情況下調(diào)用將返回準(zhǔn)確的結(jié)果,但是在統(tǒng)計(jì)計(jì)算總和的時(shí)候發(fā)生的并發(fā)更新可能并不會(huì)合并進(jìn)去。

    public long sum() {
        Cell[] as = cells; Cell a;
        //統(tǒng)計(jì)base的值和各個(gè)cell的值得和
        long sum = base;
        if (as != null) {
            for (int i = 0; i < as.length; ++i) {
                if ((a = as[i]) != null)
                    sum += a.value;
            }
        }
        return sum;
    }

public void reset()

重置所有用于統(tǒng)計(jì)總和的變量為0,可以利用這個(gè)方法創(chuàng)建一個(gè)新的加法器,但是只有在沒(méi)有并發(fā)更新的情況下才有效。因?yàn)檫@個(gè)方法本質(zhì)上是不穩(wěn)定的,所以只有當(dāng)知道沒(méi)有線程同時(shí)更新時(shí)才可以使用。

public void reset() {
        Cell[] as = cells; Cell a;
        //先把base置為0
        base = 0L;
        //然后將每個(gè)cell中的值置為0
        if (as != null) {
            for (int i = 0; i < as.length; ++i) {
                if ((a = as[i]) != null)
                    a.value = 0L;
            }
        }
    }

public long sumThenReset()

方法等價(jià)于先求和后進(jìn)行reset操作,也就是先調(diào)用sum然后調(diào)用reset。例如,這個(gè)方法可以用于求多線程情況下在一個(gè)瞬間的穩(wěn)定計(jì)算值。在方法執(zhí)行期間,如果仍然有線程在進(jìn)行并發(fā)更新,那么返回的值可能是不一致的。(參考源碼)

    public long sumThenReset() {
        Cell[] as = cells; Cell a;
        long sum = base;
        base = 0L;
        if (as != null) {
            for (int i = 0; i < as.length; ++i) {
                if ((a = as[i]) != null) {
                    //求和的同事將cell置為零
                    sum += a.value;
                    a.value = 0L;
                }
            }
        }
        return sum;
    }

public String toString()

返回和的值轉(zhuǎn)換為string

    public String toString() {
        return Long.toString(sum());
    }

public long longValue()

等價(jià)于調(diào)用sum方法。

public long longValue() {
       return sum();
}

public int intValue()

將結(jié)果執(zhí)行強(qiáng)制類型轉(zhuǎn)換為int類型。

   public int intValue() {
       return (int)sum();
   }

public float floatValue()

將結(jié)果執(zhí)行強(qiáng)制類型轉(zhuǎn)換為float類型。

   public float floatValue() {
       return (float)sum();
   }

public double doubleValue()

將結(jié)果執(zhí)行強(qiáng)制類型轉(zhuǎn)換為double類型。

    public double doubleValue() {
       return (double)sum();
   }

源碼中接下來(lái)是一個(gè)靜態(tài)內(nèi)部類
private static class SerializationProxy

是一個(gè)序列化的代理,用于避免引用序列化中的非公共Striped64

?著作權(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)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

  • 在一個(gè)方法內(nèi)部定義的變量都存儲(chǔ)在棧中,當(dāng)這個(gè)函數(shù)運(yùn)行結(jié)束后,其對(duì)應(yīng)的棧就會(huì)被回收,此時(shí),在其方法體中定義的變量將不...
    Y了個(gè)J閱讀 4,573評(píng)論 1 14
  • Java SE 基礎(chǔ): 封裝、繼承、多態(tài) 封裝: 概念:就是把對(duì)象的屬性和操作(或服務(wù))結(jié)合為一個(gè)獨(dú)立的整體,并盡...
    Jayden_Cao閱讀 2,247評(píng)論 0 8
  • 之前做過(guò)一個(gè)測(cè)試,詳情見(jiàn)這篇文章《多線程 +1操作的幾種實(shí)現(xiàn)方式,及效率對(duì)比》,當(dāng)時(shí)對(duì)這個(gè)測(cè)試結(jié)果很疑惑,反復(fù)執(zhí)行...
    bbe9e62bc5ba閱讀 1,841評(píng)論 2 68
  • 作者: 一字馬胡 轉(zhuǎn)載標(biāo)志 【2017-11-03】 更新日志 Java Striped64 Striped64...
    一字馬胡閱讀 9,094評(píng)論 11 22
  • 前言 JDK中的Hashtable是一個(gè)線程安全的K-V形式的容器,它實(shí)現(xiàn)線程安全的原理十分簡(jiǎn)單,就是在所有涉及對(duì)...
    Justlearn閱讀 2,401評(píng)論 0 11

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