Java LongAccumulator: รวมสถิติที่มีคนอัปเดตพร้อมกัน
พี่โชว์Engineering
counter เดียวที่ทุก thread แย่งอัปเดตกลายเป็นคอขวดได้ LongAccumulator กระจายการสะสมออกเป็นหลาย cell แล้วรวมเมื่ออ่าน เหมือนเปิดหลายช่องรับเหรียญก่อนเทลงถังรวมครับ เหมาะกับ metrics ที่ operation สลับลำดับได้ เช่นผลรวม, min หรือ max
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.LongAccumulator;
import java.util.stream.IntStream;
public class Main {
public static void main(String[] args) {
LongAccumulator maximum = new LongAccumulator(Long::max, Long.MIN_VALUE);
AtomicInteger calls = new AtomicInteger();
IntStream.rangeClosed(1, 1000).parallel().forEach(value -> {
maximum.accumulate(value);
calls.incrementAndGet();
});
assert calls.get() == 1000 : calls;
assert maximum.get() == 1000 : maximum.get();
long beforeReset = maximum.getThenReset();
assert beforeReset == 1000;
assert maximum.get() == Long.MIN_VALUE;
}
}
ข้อจำกัดสำคัญ
ฟังก์ชันสะสมควร associative และไม่มี side effect เพราะ runtime อาจรวม cell คนละลำดับ get() ระหว่างมี writer ไม่ใช่ atomic snapshot จึงอย่าใช้ตัดสินสิทธิ์, quota แข็ง หรือ coordination ข้าม thread สำหรับ counter ธรรมดาใช้ LongAdder; สำหรับ state ที่ต้อง linearizable ใช้ lock หรือ primitive ที่ให้สัญญานั้น
Recap
- ใช้กับสถิติที่ยอมรับการอ่านระหว่างทางแบบ approximate
- เลือก operation associative และ identity ถูกต้อง
- ไม่ใช้
get()เป็น synchronization barrier - ระวัง
getThenReset()เมื่อ writer ยังทำงาน