成人无码视频,亚洲精品久久久久av无码,午夜精品久久久久久毛片,亚洲 中文字幕 日韩 无码

資訊專欄INFORMATION COLUMN

Java多線程進(jìn)階(十七)—— J.U.C之a(chǎn)tomic框架:LongAdder

fengxiuping / 2172人閱讀

摘要:在并發(fā)量較低的環(huán)境下,線程沖突的概率比較小,自旋的次數(shù)不會很多。比如有三個,每個線程對增加。的核心方法還是通過例子來看假設(shè)現(xiàn)在有一個對象,四個線程同時對進(jìn)行累加操作。

本文首發(fā)于一世流云的專欄:https://segmentfault.com/blog...
一、LongAdder簡介

JDK1.8時,java.util.concurrent.atomic包中提供了一個新的原子類:LongAdder
根據(jù)Oracle官方文檔的介紹,LongAdder在高并發(fā)的場景下會比它的前輩————AtomicLong 具有更好的性能,代價是消耗更多的內(nèi)存空間:

那么,問題來了:

為什么要引入LongAdderAtomicLong在高并發(fā)的場景下有什么問題嗎?  如果低并發(fā)環(huán)境下,LongAdderAtomicLong性能差不多,那LongAdder是否就可以替代AtomicLong了?
為什么要引入LongAdder?

我們知道,AtomicLong是利用了底層的CAS操作來提供并發(fā)性的,比如addAndGet方法:

上述方法調(diào)用了Unsafe類的getAndAddLong方法,該方法是個native方法,它的邏輯是采用自旋的方式不斷更新目標(biāo)值,直到更新成功。

在并發(fā)量較低的環(huán)境下,線程沖突的概率比較小,自旋的次數(shù)不會很多。但是,高并發(fā)環(huán)境下,N個線程同時進(jìn)行自旋操作,會出現(xiàn)大量失敗并不斷自旋的情況,此時AtomicLong的自旋會成為瓶頸。

這就是LongAdder引入的初衷——解決高并發(fā)環(huán)境下AtomicLong的自旋瓶頸問題。

LongAdder快在哪里?

既然說到LongAdder可以顯著提升高并發(fā)環(huán)境下的性能,那么它是如何做到的?這里先簡單的說下LongAdder的思路,第二部分會詳述LongAdder的原理。

我們知道,AtomicLong中有個內(nèi)部變量value保存著實際的long值,所有的操作都是針對該變量進(jìn)行。也就是說,高并發(fā)環(huán)境下,value變量其實是一個熱點,也就是N個線程競爭一個熱點。

LongAdder的基本思路就是分散熱點,將value值分散到一個數(shù)組中,不同線程會命中到數(shù)組的不同槽中,各個線程只對自己槽中的那個值進(jìn)行CAS操作,這樣熱點就被分散了,沖突的概率就小很多。如果要獲取真正的long值,只要將各個槽中的變量值累加返回。

這種做法有沒有似曾相識的感覺?沒錯,ConcurrentHashMap中的“分段鎖”其實就是類似的思路。

LongAdder能否替代AtomicLong?

回答這個問題之前,我們先來看下LongAdder提供的API:

可以看到,LongAdder提供的API和AtomicLong比較接近,兩者都能以原子的方式對long型變量進(jìn)行增減。

但是AtomicLong提供的功能其實更豐富,尤其是addAndGet、decrementAndGet、compareAndSet這些方法。

addAndGet、decrementAndGet除了單純的做自增自減外,還可以立即獲取增減后的值,而LongAdder則需要做同步控制才能精確獲取增減后的值。如果業(yè)務(wù)需求需要精確的控制計數(shù),做計數(shù)比較,AtomicLong也更合適。

另外,從空間方面考慮,LongAdder其實是一種“空間換時間”的思想,從這一點來講AtomicLong更適合。當(dāng)然,如果你一定要跟我杠現(xiàn)代主機(jī)的內(nèi)存對于這點消耗根本不算什么,那我也辦法。

總之,低并發(fā)、一般的業(yè)務(wù)場景下AtomicLong是足夠了。如果并發(fā)量很多,存在大量寫多讀少的情況,那LongAdder可能更合適。適合的才是最好的,如果真出現(xiàn)了需要考慮到底用AtomicLong好還是LongAdder的業(yè)務(wù)場景,那么這樣的討論是沒有意義的,因為這種情況下要么進(jìn)行性能測試,以準(zhǔn)確評估在當(dāng)前業(yè)務(wù)場景下兩者的性能,要么換個思路尋求其它解決方案。

最后,給出國外一位博主對LongAdder和AtomicLong的性能評測,以供參考:http://blog.palominolabs.com/...

二、LongAdder原理

之前說了,AtomicLong是多個線程針對單個熱點值value進(jìn)行原子操作。而LongAdder是每個線程擁有自己的槽,各個線程一般只對自己槽中的那個值進(jìn)行CAS操作。

比如有三個ThreadA、ThreadB、ThreadC,每個線程對value增加10。

對于AtomicLong,最終結(jié)果的計算始終是下面這個形式:
$$ value = 10 + 10 + 10 = 30 $$

但是對于LongAdder來說,內(nèi)部有一個base變量,一個Cell[]數(shù)組。
base變量:非競態(tài)條件下,直接累加到該變量上
Cell[]數(shù)組:競態(tài)條件下,累加個各個線程自己的槽Cell[i]
最終結(jié)果的計算是下面這個形式:
$$ value = base + sum_{i=0}^nCell[i] $$

LongAdder的內(nèi)部結(jié)構(gòu)

LongAdder只有一個空構(gòu)造器,其本身也沒有什么特殊的地方,所有復(fù)雜的邏輯都在它的父類Striped64中。

來看下Striped64的內(nèi)部結(jié)構(gòu),這個類實現(xiàn)一些核心操作,處理64位數(shù)據(jù)。
Striped64只有一個空構(gòu)造器,初始化時,通過Unsafe獲取到類字段的偏移量,以便后續(xù)CAS操作:

上面有個比較特殊的字段是threadLocalRandomProbe,可以把它看成是線程的hash值。這個后面我們會講到。

定義了一個內(nèi)部Cell類,這就是我們之前所說的槽,每個Cell對象存有一個value值,可以通過Unsafe來CAS操作它的值:

其它的字段:
可以看到Cell[]就是之前提到的槽數(shù)組,base就是非并發(fā)條件下的基數(shù)累計值。

LongAdder的核心方法

還是通過例子來看:
假設(shè)現(xiàn)在有一個LongAdder對象la,四個線程A、B、C、D同時對la進(jìn)行累加操作。

LongAdder la = new LongAdder();
la.add(10);

ThreadA調(diào)用add方法(假設(shè)此時沒有并發(fā)):

初始時Cell[]為null,base為0。所以ThreadA會調(diào)用casBase方法(定義在Striped64中),因為沒有并發(fā),CAS操作成功將base變?yōu)?0:

可以看到,如果線程A、B、C、D線性執(zhí)行,那casBase永遠(yuǎn)不會失敗,也就永遠(yuǎn)不會進(jìn)入到base方法的if塊中,所有的值都會累積到base中。
那么,如果任意線程有并發(fā)沖突,導(dǎo)致caseBase失敗呢?

失敗就會進(jìn)入if方法體:

這個方法體會先再次判斷Cell[]槽數(shù)組有沒初始化過,如果初始化過了,以后所有的CAS操作都只針對槽中的Cell;否則,進(jìn)入longAccumulate方法。

整個add方法的邏輯如下圖:

可以看到,只有從未出現(xiàn)過并發(fā)沖突的時候,base基數(shù)才會使用到,一旦出現(xiàn)了并發(fā)沖突,之后所有的操作都只針對Cell[]數(shù)組中的單元Cell。
如果Cell[]數(shù)組未初始化,會調(diào)用父類的longAccumelate去初始化Cell[],如果Cell[]已經(jīng)初始化但是沖突發(fā)生在Cell單元內(nèi),則也調(diào)用父類的longAccumelate,此時可能就需要對Cell[]擴(kuò)容了。

這也是LongAdder設(shè)計的精妙之處:盡量減少熱點沖突,不到最后萬不得已,盡量將CAS操作延遲。

Striped64的核心方法

我們來看下Striped64的核心方法longAccumulate到底做了什么:

上述代碼首先給當(dāng)前線程分配一個hash值,然后進(jìn)入一個自旋,這個自旋分為三個分支:

CASE1:Cell[]數(shù)組已經(jīng)初始化

CASE2:Cell[]數(shù)組未初始化

CASE3:Cell[]數(shù)組正在初始化中

CASE2:Cell[]數(shù)組未初始化

我們之前討論了,初始時Cell[]數(shù)組還沒有初始化,所以會進(jìn)入分支②:

首先會將cellsBusy置為1-加鎖狀態(tài)

然后,初始化Cell[]數(shù)組(初始大小為2),根據(jù)當(dāng)前線程的hash值計算映射的索引,并創(chuàng)建對應(yīng)的Cell對象,Cell單元中的初始值x就是本次要累加的值。

CASE3:Cell[]數(shù)組正在初始化中

如果在初始化過程中,另一個線程ThreadB也進(jìn)入了longAccumulate方法,就會進(jìn)入分支③:

可以看到,分支③直接操作base基數(shù),將值累加到base上。

CASE1:Cell[]數(shù)組已經(jīng)初始化

如果初始化完成后,其它線程也進(jìn)入了longAccumulate方法,就會進(jìn)入分支①:

整個longAccumulate的流程圖如下:

LongAdder的sum方法

最后,我們來看下LongAddersum方法:

sum求和的公式就是我們開頭說的:
$$ value = base + sum_{i=0}^nCell[i] $$

需要注意的是,這個方法只能得到某個時刻的近似值,這也就是LongAdder并不能完全替代LongAtomic的原因之一。

三、LongAdder的其它兄弟

JDK1.8時,java.util.concurrent.atomic包中,除了新引入LongAdder外,還有引入了它的三個兄弟類:LongAccumulator、DoubleAdder、DoubleAccumulator

LongAccumulator

LongAccumulatorLongAdder的增強版。LongAdder只能針對數(shù)值的進(jìn)行加減運算,而LongAccumulator提供了自定義的函數(shù)操作。其構(gòu)造函數(shù)如下:

通過LongBinaryOperator,可以自定義對入?yún)⒌娜我獠僮?,并返回結(jié)果(LongBinaryOperator接收2個long作為參數(shù),并返回1個long)

LongAccumulator內(nèi)部原理和LongAdder幾乎完全一樣,都是利用了父類Striped64longAccumulate方法。這里就不再贅述了,讀者可以自己閱讀源碼。

DoubleAdder和DoubleAccumulator

從名字也可以看出,DoubleAdderDoubleAccumulator用于操作double原始類型。

LongAdder的唯一區(qū)別就是,其內(nèi)部會通過一些方法,將原始的double類型,轉(zhuǎn)換為long類型,其余和LongAdder完全一樣:

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://hztianpu.com/yun/76607.html

相關(guān)文章

  • Java線程進(jìn)階(一)—— J.U.C并發(fā)包概述

    摘要:整個包,按照功能可以大致劃分如下鎖框架原子類框架同步器框架集合框架執(zhí)行器框架本系列將按上述順序分析,分析所基于的源碼為。后,根據(jù)一系列常見的多線程設(shè)計模式,設(shè)計了并發(fā)包,其中包下提供了一系列基礎(chǔ)的鎖工具,用以對等進(jìn)行補充增強。 showImg(https://segmentfault.com/img/remote/1460000016012623); 本文首發(fā)于一世流云專欄:https...

    anonymoussf 評論0 收藏0
  • Java線程進(jìn)階(十五)—— J.U.Catomic框架Atomic數(shù)組

    摘要:注意原子數(shù)組并不是說可以讓線程以原子方式一次性地操作數(shù)組中所有元素的數(shù)組。類的方法返回指定類型數(shù)組的元素所占用的字節(jié)數(shù)。,是將轉(zhuǎn)換為進(jìn)制,然后從左往右數(shù)連續(xù)的個數(shù)。 showImg(https://segmentfault.com/img/remote/1460000016012145); 本文首發(fā)于一世流云的專欄:https://segmentfault.com/blog... 一...

    lunaticf 評論0 收藏0
  • Java線程進(jìn)階(十二)—— J.U.Catomic框架:Unsafe類

    摘要:本身不直接支持指針的操作,所以這也是該類命名為的原因之一。中的許多方法,內(nèi)部其實都是類在操作。 showImg(https://segmentfault.com/img/remote/1460000016012251); 本文首發(fā)于一世流云的專欄:https://segmentfault.com/blog... 一、Unsafe簡介 在正式的開講 juc-atomic框架系列之前,有...

    趙連江 評論0 收藏0
  • Java線程進(jìn)階(十六)—— J.U.Catomic框架:FieldUpdater

    摘要:所謂,就是可以以一種線程安全的方式操作非線程安全對象的某些字段。我們來對上述代碼進(jìn)行改造賬戶類改造引入通過操作字段調(diào)用方,并未做任何改變上述代碼,無論執(zhí)行多少次,最終結(jié)果都是,因為這回是線程安全的。這也是整個包的設(shè)計理念之一。 showImg(https://segmentfault.com/img/remote/1460000016012109); 本文首發(fā)于一世流云的專欄:http...

    darcrand 評論0 收藏0
  • Java線程進(jìn)階(十三)—— J.U.Catomic框架AtomicInteger

    摘要:顧名思義,是類型的線程安全原子類,可以在應(yīng)用程序中以原子的方式更新值。創(chuàng)建對象先來看下對象的創(chuàng)建。也就是說當(dāng)一個線程修改一個共享變量時,其它線程能立即讀到這個修改的值。 showImg(https://segmentfault.com/img/remote/1460000016012210); 本文首發(fā)于一世流云的專欄:https://segmentfault.com/blog... ...

    darkbug 評論0 收藏0

發(fā)表評論

0條評論

閱讀需要支付1元查看
<