LongAccumulator和DoubleAccumulator类如何工作?
Java 8中的兩個(gè)新類值得關(guān)注: LongAccumulator和DoubleAccumulator 。 它們旨在安全地跨線程安全地累積 (稍后將進(jìn)一步說明)。 一個(gè)測試值一千個(gè)單詞,所以它是這樣工作的:
class AccumulatorSpec extends Specification {public static final long A = 1public static final long B = 2public static final long C = 3public static final long D = -4public static final long INITIAL = 0Ldef 'should add few numbers'() {given:LongAccumulator accumulator = new LongAccumulator({ long x, long y -> x + y }, INITIAL)when:accumulator.accumulate(A)accumulator.accumulate(B)accumulator.accumulate(C)accumulator.accumulate(D)then:accumulator.get() == INITIAL + A + B + C + D}因此,累加器采用二進(jìn)制運(yùn)算符并將初始值與每個(gè)累加值組合在一起。 這意味著((((0 + 1) + 2) + 3) + -4)等于2 。 別走開,還有更多。 累加器也可以采用其他運(yùn)算符,如以下用例所示:
def 'should accumulate numbers using operator'() {given:LongAccumulator accumulator = new LongAccumulator(operator, initial)when:accumulator.accumulate(A)accumulator.accumulate(B)accumulator.accumulate(C)accumulator.accumulate(D)then:accumulator.get() == expectedwhere:operator | initial || expected{x, y -> x + y} | 0 || A + B + C + D{x, y -> x * y} | 1 || A * B * C * D{x, y -> Math.max(x, y)} | Integer.MIN_VALUE || max(A, B, C, D){x, y -> Math.min(x, y)} | Integer.MAX_VALUE || min(A, B, C, D) }顯然,累加器在設(shè)計(jì)用于繁重的多線程環(huán)境下也能正常工作。 現(xiàn)在的問題是, LongAccumulator中允許進(jìn)行其他哪些操作(這也適用于DoubleAccumulator ),為什么? JavaDoc這次不是很正式(粗體):
不能保證并且不能依賴于線程內(nèi)或線程間的累加順序,因此此類僅適用于累加順序無關(guān)緊要的函數(shù)。 所提供的累加器功能應(yīng)無副作用 ,因?yàn)楫?dāng)嘗試更新由于線程間爭用而失敗時(shí),可以重新應(yīng)用該累加器功能 。 應(yīng)用該函數(shù)時(shí),將當(dāng)前值作為其第一個(gè)參數(shù),并將給定的update作為第二個(gè)參數(shù)。
為了了解LongAccumulator工作原理,允許哪種類型的操作以及為何如此之快(因?yàn)榕cAtomicLong相比,它是如此之快),讓我們從后面開始get()方法:
transient volatile long base; transient volatile Cell[] cells;private final LongBinaryOperator function;public long get() {Cell[] as = cells; Cell a;long result = base;if (as != null) {for (int i = 0; i < as.length; ++i) {if ((a = as[i]) != null)result = function.applyAsLong(result, a.value);}}return result; }可以將其重寫為不完全等效,但更易于閱讀:
public long get() {long result = base;for (Cell cell : cells)result = function.applyAsLong(result, cell.value);return result; }甚至在功能上沒有內(nèi)部狀態(tài):
public long get() {return Arrays.stream(cells).map(s -> s.value).reduce(base, function::applyAsLong); }我們清楚地看到有一些內(nèi)部cells數(shù)組,最后我們必須遍歷該數(shù)組并將操作符函數(shù)順序應(yīng)用于每個(gè)元素。 事實(shí)證明, LongAccumulator具有兩種用于累加值的機(jī)制:單個(gè)base計(jì)數(shù)器和在鎖線程爭用較高的情況下的值數(shù)組。 如果在沒有鎖爭用的情況下使用LongAccumulator ,則僅使用單個(gè)volatile base變量和CAS操作,就像在AtomicLong 。 但是,如果CAS失敗,則此類退回到一組值。 您不想看到實(shí)現(xiàn),它有90行,有時(shí)會(huì)有8個(gè)嵌套層。 您需要知道的是,它使用簡單的算法始終將給定線程分配給同一單元(提高了緩存的局部性)。 從現(xiàn)在開始,該線程具有其自己的,幾乎是私有的計(jì)數(shù)器副本。 它與其他幾個(gè)線程共享此副本,但并非與所有其他線程共享-它們具有自己的單元。 因此,最終您得到的是必須匯總的半計(jì)算計(jì)數(shù)器數(shù)組。 這就是您在get()方法中看到的。
這又使我們想到一個(gè)問題, LongAccumulator中允許使用LongAccumulator op符( op )。 我們知道在低負(fù)載下相同的累積順序?qū)?dǎo)致:
((I op A) op B) //get()這意味著所有值都聚集在基本變量中,并且不使用任何計(jì)數(shù)器數(shù)組。 但是,在高負(fù)載下, LongAccumulator會(huì)將工作分解為兩個(gè)鏟斗(單元),然后LongAccumulator鏟斗也進(jìn)行蓄積:
(I op A) //cell 1 (I op B) //cell 2(I op A) op (I op B) //get()或相反亦然:
(I op B) //cell 1 (I op A) //cell 2(I op B) op (I op A) //get()顯然,所有對get()調(diào)用都應(yīng)產(chǎn)生相同的結(jié)果,但這都取決于所提供的op符的屬性( + , * , max等)。
可交換的
我們無法控制單元的順序及其分配方式。 這就是((I op A) op (I op B))和((I op B) op (I op A))必須返回相同結(jié)果的原因。 更緊湊地說,我們正在尋找這樣的運(yùn)算符op ,其中每個(gè)X和Y X op Y = Y op X 這意味著op必須是可交換的 。
中性元素(身份)
單元格使用標(biāo)識(初始)值I進(jìn)行邏輯初始化。 我們無法控制單元的數(shù)量和順序,因此身份值可??以按任意順序多次應(yīng)用。 但這是一個(gè)實(shí)現(xiàn)細(xì)節(jié),因此不應(yīng)影響結(jié)果。 更確切地說,對于每個(gè)X和任何op :
X op I = I op X = X這意味著對于運(yùn)算符op每個(gè)參數(shù)X ,標(biāo)識(初始)值I必須為中性值。
關(guān)聯(lián)性
假設(shè)我們有以下單元格:
I op A // cell 1 I op B // cell 2 I op C // cell 3 ((I op A) op (I op B)) op (I op C) //get()但是下一次他們的布置有所不同
I op C // cell 1 I op B // cell 2 I op A // cell 2 ((I op C) op (I op B)) op (I op A) //get()知道op是可交換的,而I是中性元素,我們可以證明(對于每個(gè)A , B和C ):
((I op A) op (I op B)) op (I op C) = ((I op C) op (I op B)) op (I op A) (A op B) op C = (C op B) op A (A op B) op C = A op (B op C)這證明op必須具有關(guān)聯(lián)性 , LongAccumulator才能真正起作用。
包起來
LongAccumulator和DoubleAccumulator是JDK 8中新增的高度專業(yè)化的類。JavaDoc相當(dāng)荒謬,但是我們嘗試證明操作員和初始值必須滿足的屬性才能使它們完成工作。 我們知道運(yùn)算符必須是關(guān)聯(lián)的 , 可交換的并且具有中性元素。 如果JavaDoc明確聲明它必須是阿貝爾的類半體動(dòng)物 ;-),那就更好了。 然而,出于實(shí)際目的,這些累加器僅用于加,乘,最小和最大值,因?yàn)樗鼈兪俏ㄒ话l(fā)揮良好作用的有用運(yùn)算符(帶有適當(dāng)?shù)闹行栽?#xff09;。 例如,減法和除法不是關(guān)聯(lián)和可交換的,因此不可能工作。 更糟的是,累加器只會(huì)表現(xiàn)得不確定。
翻譯自: https://www.javacodegeeks.com/2015/06/how-longaccumulator-and-doubleaccumulator-classes-work.html
總結(jié)
以上是生活随笔為你收集整理的LongAccumulator和DoubleAccumulator类如何工作?的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 答疑解惑是什么意思 答疑解惑的含义
- 下一篇: jsf 传参数_在JSF 2中对定制验证