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