นากแม่น้ำสีมิ้นต์ต้นฉบับกำลังรวมเมล็ดจากหลายรางลงเครื่องชั่งเดียวโดยไม่ให้รางชนกัน

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 ยังทำงาน