LongAdder簡(jiǎn)單介紹
LongAdder類似于AtomicLong是原子性遞增或者遞減類臼勉,AtomicLong已經(jīng)通過CAS提供了非阻塞的原子性操作附井,相比使用阻塞算法的同步器來說性能已經(jīng)很好了便斥,但是JDK開發(fā)組并不滿足瘩扼,因?yàn)樵诜浅8叩牟l(fā)請(qǐng)求下AtomicLong的性能不能讓他們接受炉奴,雖然AtomicLong使用CAS但是CAS失敗后還是通過無限循環(huán)的自旋鎖不斷嘗試的
public final long incrementAndGet() {
for (;;) {
long current = get();
long next = current + 1;
if (compareAndSet(current, next))
return next;
}
}
在高并發(fā)下N多線程同時(shí)去操作一個(gè)變量會(huì)造成大量線程CAS失敗然后處于自旋狀態(tài)擦酌,這大大浪費(fèi)了cpu資源,降低了并發(fā)性垂攘。那么既然AtomicLong性能由于過多線程同時(shí)去競(jìng)爭(zhēng)一個(gè)變量的更新而降低的维雇,那么如果把一個(gè)變量分解為多個(gè)變量,讓同樣多的線程去競(jìng)爭(zhēng)多個(gè)資源那么性能問題不就解決了晒他?是的吱型,JDK8提供的LongAdder就是這個(gè)思路。下面通過圖形來標(biāo)示兩者不同陨仅。
如圖AtomicLong是多個(gè)線程同時(shí)競(jìng)爭(zhēng)同一個(gè)變量津滞。
如圖LongAdder則是內(nèi)部維護(hù)多個(gè)變量,每個(gè)變量初始化都0灼伤,在同等并發(fā)量的情況下触徐,爭(zhēng)奪單個(gè)變量的線程量會(huì)減少這是變相的減少了爭(zhēng)奪共享資源的并發(fā)量,另外多個(gè)線程在爭(zhēng)奪同一個(gè)原子變量時(shí)候如果失敗并不是自旋CAS重試狐赡,而是嘗試獲取其他原子變量的鎖撞鹉,最后獲取當(dāng)前值時(shí)候是把所有變量的值累加后返回的。
LongAdder維護(hù)了一個(gè)延遲初始化的原子性更新數(shù)組和一個(gè)基值變量base.數(shù)組的大小保持是2的N次方大小颖侄,數(shù)組表的下標(biāo)使用每個(gè)線程的hashcode值的掩碼表示鸟雏,數(shù)組里面的變量實(shí)體是Cell類型,Cell類型是AtomicLong的一個(gè)改進(jìn)览祖,用來減少緩存的爭(zhēng)用孝鹊,對(duì)于大多數(shù)原子操作字節(jié)填充是浪費(fèi)的,因?yàn)樵有圆僮鞫际菬o規(guī)律的分散在內(nèi)存中進(jìn)行的展蒂,多個(gè)原子性操作彼此之間是沒有接觸的又活,但是原子性數(shù)組元素彼此相鄰存放將能經(jīng)常共享緩存行,所以這在性能上是一個(gè)提升玄货。
另外由于Cells占用內(nèi)存是相對(duì)比較大的皇钞,所以一開始并不創(chuàng)建,而是在需要時(shí)候在創(chuàng)建松捉,也就是惰性加載夹界,當(dāng)一開始沒有空間時(shí)候,所有的更新都是操作base變量,
自旋鎖cellsBusy用來初始化和擴(kuò)容數(shù)組表使用可柿,這里沒有必要用阻塞鎖鸠踪,當(dāng)一次線程發(fā)現(xiàn)當(dāng)前下標(biāo)的元素獲取鎖失敗后,會(huì)嘗試獲取其他下表的元素的鎖复斥。更詳細(xì)的介紹可以參考作者《Java并發(fā)編程之美》一書营密。