原子操作之 LongAccumulator
1. 前言
今天為大家介紹原子操作之 LongAccumulator。此工具位于 java.util.concurrent.atomic 包中。
本節先介紹 LongAccumulator 工具類的基本概念和最基本用法,之后給出 LongAccumulator 工具類最常用的場合說明,然后通過簡單的編碼實現一個實際案例,最后帶領大家熟悉 LongAccumulator 最常用的一些編程方法,讓大家進一步加深對 LongAccumulator 工具類的理解。
下面我們正式開始介紹吧。
2. 概念介紹
相比 LongAdder,LongAccumulator 工具類提供了更靈活更強大的功能。不但可以指定計算結果的初始值,相比 LongAdder 只能對數值進行加減運算,LongAccumulator 還能自定義計算規則,比如做乘法運行,或其他任何你想要的計算規則。這樣描述是不是有點抽象,別著急,看下面的圖示。
我們看下面 LongAccumulator 工具類的基本用法。
3. 基本用法
// 首先創建一個雙目運算器對象,這個對象實現了計算規則。
LongBinaryOperator longBinaryOperator = new LongBinaryOperator() {
@Override
public long applyAsLong(long left, long right) {
...
}
}
// 接著使用構造方法創建一個 LongAccumulator 對象,這個對象的第1個參數就是一個雙目運算器對象,第二個參數是累加器的初始值。
LongAccumulator longAccumulator = new LongAccumulator(longBinaryOperator, 0);
...
// 調用累加方法
longAccumulator.accumulate(1000)
// 調用結果獲取方法
long result = longAccumulator.get();
...
是不是簡單又強大!LongAccumulator 在我們日常實踐中,到底應該應用在哪些場合比較合適呢?下面我們給出最常用的場景說明。
4. 常用場景
LongAccumulator 經常用于自定義運算規則場景下的多線程并發場合。一些簡單的累加計算可以直接使用我們之前課程中介紹的工具類,但是當運行規則比較復雜或者 JDK 沒有提供對應的工具類時,可以考慮 LongAccumulator 輔助實現。當然所有可使用 LongAdder 的場合都可使用 LongAccumulator 代替,但是沒有必要。
下面我們用 LongAccumulator 工具類實現上一節中的生活實例,為了簡化敘述,本節我們只統計男性客戶總數量。請看下面的代碼。
5. 場景案例
import java.util.concurrent.atomic.LongAccumulator;
public class LongAccumulatorTest {
// 此處的運算規則是累加,所以創建一個加法雙目運算器對象作為構造函數的第一個參數。
// 將第二個參數置為0,表示累加初始值。
// maleCount 對象代表當天所有進入商場的男性客戶總數量。
private static LongAccumulator maleCount = new LongAccumulator(new LongBinaryOperator() {
// 此方法用于實現計算規則
@Override
public long applyAsLong(long left, long right) {
// 在本例中使用加法計算規則
return left + right;
}
}, 0);
public static void main(String[] args) {
// 定義30個商場入口檢測設備
for (int i = 1; i <= 30; i++) {
MonitoringDevice monitoringDevice = new MonitoringDevice(maleCount, i);
// 開啟檢測設備進行檢測
new Thread(monitoringDevice).start();
}
}
}
在上面的代碼中,首先創建一個 LongAccumulator 對象表示統計結果,然后創建了 30 個商場入口檢測設備模擬檢測識別,接下來每個檢測設備如何動作呢,看下面的代碼。
import java.util.Random;
import java.util.concurrent.atomic.LongAccumulator;
/**
* 模擬設備
*/
public class MonitoringDevice implements Runnable {
private LongAccumulator maleCount;
private String monitoringDeviceNo;
public MonitoringDevice(LongAccumulator maleCount, int monitoringDeviceNo) {
this.maleCount = maleCount;
this.monitoringDeviceNo = "第" + monitoringDeviceNo + "監控采集處";
}
/**
* 設備運行的處理邏輯
*/
public void run() {
while (true) {
// 監測處理 (監測設備輸出1代表男性,0代表女性,其他代表未能識別,此處隨機產生監測結果)
try {
Thread.sleep(new Random().nextInt(3000));
} catch (Exception e) {}
int monitoringDeviceOutput = new Random().nextInt(3);
// 對監測結果進行統計
switch (monitoringDeviceOutput) {
case 1: maleCount.accumulate(1);
System.out.println("統計結果: maleCount=" + maleCount.get());
break;
default:
System.out.println("忽略統計");
break;
}
}
}
}
在 MonitoringDevice 類中,首先模擬監測設備輸出,然后將輸出結果使用 add () 進行統計累加,使用 sum () 輸出累加結果。運行一段時間后運行結果如下。
...
忽略統計
統計結果: maleCount=50
...
上面的示例中,使用 LongAccumulator 實現了上一節中相同的需求。對比觀察,能夠體會到 LongAccumulator 工具類更靈活的地方,但同時也更復雜一些。
至此,大家對 LongAccumulator 已經有了初步的理解,接下來我們繼續豐富對 LongAccumulator 工具類的認識。
6. 核心方法介紹
除過上面代碼中使用的最基本的 accumulate (int)、get () 方法之外,我們再介紹兩個方法的使用。
- reset () 方法
將累加器值置為 0,即為后繼使用重新歸位。
- getThenReset () 方法
此方法邏輯等同于先調用 get () 方法再調用 reset () 方法。
7. 小結
本節通過一個簡單的例子,介紹了 LongAccumulator 的基本用法。在 java.util.concurrent.atomic 包中還存在類似的工具類,如 DoubleAccumulator,用法大同小異,希望大家在日常研發中多比較多總結,早日掌握之。