簡單看看LongAccumulator


  上篇博客我們看了AtomicLong和LongAdder的由來,但是有的時候我們想一下,LongAdder這個類也有點局限性,因為只能是每一次都+1,那有沒有辦法每次+2呢?或者每次乘以2?說得更抽象一點,我們能不能自己指定規則呢?干嘛老是傻乎乎的+1呢?

  於是就有了LongAccumulator這個累加器,這個累加器更加抽象,前面使用的LongAdder只不過是這個累加器的一個特例,由此我們可以猜出這個累加器功能更加強大,但是需要我們自己的定制規則;

  前提:看本篇博客的人應該熟悉jdk8中的函數式編程,jdk8是在2014年3月18日就推出了,到現在已經將近6年了,但是我們還是很多人在用着jdk8寫着jdk7版本的代碼,哎,無力吐槽!既然不能改變別人就改變自己吧!

 

一. 簡單使用LongAccumulator累加器

  我們先看看這個累加器的構成,如下所示:

public class LongAccumulator extends Striped64 implements Serializable {
    //這是一個函數式接口,函數描述符是(T,T)->T ,兩個相同類型的數據按照某種規則運算,返回相同類型的數據
    private final LongBinaryOperator function;
    //這個是累加器的初始值,也就是相當於LongAdder的base字段,就好像LongAdder的初始值是0一樣,但是這里的累加器初始值可以不為0
    private final long identity;

    //可以看到構造函數中要傳進去一個函數行為,還有初始值
    public LongAccumulator(LongBinaryOperator accumulatorFunction, long identity) {
        this.function = accumulatorFunction;
        base = this.identity = identity;
    }

//這個就是函數式接口,用這個@FunctionalInterface注解標識,函數描述符是(T,T)->T
@FunctionalInterface
public interface LongBinaryOperator {
    long applyAsLong(long left, long right);
}

 

  下面我們就簡單的使用了,看看LongAccumulator在實例化的時候,我們使用Lambda表達式指定了累加規則,其實就是將傳進去的數據和初始值進行累加,而傳進去的數字可以在accumulate方法里面動態指定!

package com.example.demo.study;

import java.util.concurrent.atomic.LongAccumulator;

public class Study0127 {

    //這里使用LongAccumulator類
    public LongAccumulator num = new LongAccumulator((a,b)->a+b, 0);
    

    //每次調用這個方法,都會對全局變量加一操作,執行10000次
    public void sum() {
        for (int i = 0; i < 10000; i++) {
            //LongAccumulator類的自增操作,這里的參數可以指定每次增加的數字
            num.accumulate(1);
            System.out.println("當前num的值為num= "+ num);
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Study0127 demo = new Study0127();
        //下面就是新建兩個線程,分別調用一次sum方法
        new Thread(new Runnable() {
            @Override
            public void run() {
                demo.sum();
            }
        }).start();

        new Thread(new Runnable() {

            @Override
            public void run() {
                demo.sum();
            }
        }).start();    
    }
}

 

 

  如果我們每個線程都要對一個初始值乘以2,那么可以像下面這樣修改,只需要修改lambda表達式,初始值還有線程每進行一次運算所需要的數字就行了,有興趣的可以自己試試

....省略部分代碼
//注意這里的lambda表達式
public LongAccumulator num = new LongAccumulator((a,b)->a*b, 1);
    
//每次調用這個方法,都會對全局變量乘以2,每個線程都乘以4次
public void sum() {
    for (int i = 1; i < 5; i++) {
     //LongAccumulator類指定一個數字
      num.accumulate(2);
      System.out.println("當前num的值為num= "+ num);
    }
}
.....省略部分代碼

 

 

二.走進LongAccumulator

  其實沒什么特別想看的吧,就是很多的兩個if語句那里一堆判斷,注意這里的base是我們實例化LongAccumulator累加器時候傳進去的初始值

  第一個if語句,會嘗試更新將初始值和每一步要計算的值運算返回結果,更新初始值,如果初始值更新完畢之后就不會往下走;如果初始值更新失敗,那么往下走

  第二個if語句,只有當找到了對應的Cell數組中的Cell元素用CAS更新值的時候失敗才會進入到if里面

  用上面的乘以2的例子說一下,一個線程,初始值base是1,乘以2運算,結果是2,此時使用CAS嘗試將初始值base設置為2,如果成功那么當前線程執行完畢;設置失敗(可能是其他線程已經更新了base的值了),那么就去找Cell數組中的某個Cell元素去更新它,更新成功,則線程執行完畢;更新失敗,去初始化Cell數組、擴容Cell數組等操作了

 public void accumulate(long x) {
    Cell[] as; long b, v, r; int m; Cell a;
    //注意這里這個if語句的第二個判斷條件,這里就是傳進去function.applyAsLong(b = base, x)),會調用我們傳進去的lambda行為計算
    if ((as = cells) != null ||  (r = function.applyAsLong(b = base, x)) != b && !casBase(b, r)) {
        boolean uncontended = true;
        if (as == null || (m = as.length - 1) < 0 || (a = as[getProbe() & m]) == null || 
             !(uncontended = (r = function.applyAsLong(v = a.value, x)) == v || a.cas(v, r)))
             //下面這個方法上次說了,就是對Cell數組的初始化,擴容和新創建Cell的操作,只不過在LongAdder中傳遞進去的function是null,
             //而這里傳進去的就是我們自己定義的lambda行為,就不進去看了,基本一樣
            longAccumulate(x, function, uncontended);
    }
}
//用CAS更新base的值
 final boolean casBase(long cmp, long val) {
    return UNSAFE.compareAndSwapLong(this, BASE, cmp, val);
}

  

  這個longAccumulate()方法上一篇已經分析過了,就不多說了,唯一不同的就是這次的fn不為null,所以這里不是v+x,而是fn.applyAsLong(v, x),也就是我們傳進去的lambda表達式

 

 

  這次這個類沒什么新的東西吧,就是用到了jdk8的行為參數化,我們傳進去的不再是一個數值或者字符串,更應該說是傳進去一個函數,熟悉js的人應該知道,有興趣的可以了解一下!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM