Does Java LongAdder's increment() & sum() prevent getting the same value twice?

Viewed 2201

Currently I am using AtomicLong as a synchronized counter in my application, but I have found that with high concurrency/contention, e.g. with 8 threads my throughput is much lower (75% lower) then single-threaded for obvious reasons (e.g. concurrent CAS).

Use case: A counter variable which

  • is updated by multiple threads concurrently
  • has high write contention, basically every usage in a thread will consist of a write with an immediate read afterwards
  • Requirement is that each read from the counter (immediately after the writing) gets a unique incremented value. It is not required that each retrieved counter value is increasing in the same order as the different threads(writers) increment the value.

So I tried to replace AtomicLong with a LongAdder, and indeed it looks from my measurements that my throughput with 8 threads is much better - (only) about 20% lower than single-threaded (compared to 75%).

However I'm not sure I correctly understand the way LongAdder works. The JavaDoc says:

This class is usually preferable to AtomicLong when multiple threads update a common sum that is used for purposes such as collecting statistics, not for fine-grained synchronization control.

and for sum()

Returns the current sum. The returned value is NOT an atomic snapshot; invocation in the absence of concurrent updates returns an accurate result, but concurrent updates that occur while the sum is being calculated might not be incorporated.

What is meant by fine-grained synchronization control ... From looking at this so question and the source of AtomicLong and Striped64, I think I understand that if the update on an AtomicLong is blocked because of a CAS instruction issued by another thread, the update is stored thread-local and accumulated later to get some eventual consistency. So without further synchronization and because the incrementAndGet() in LongAdder is not atomic but two instructions, I fear the following is possible:

private static final LongAdder counter = new LongAdder(); // == 0
// no further synchronisation happening in java code
Thread#1 :  counter.increment();
Thread#2 :  counter.increment();  // CAS T#1 still ongoing, storing +1 thread-locally
Thread#2 :  counter.sum();       // == 1
Thread#3 :  counter.increment();  // CAS T#1 still ongoing, storing +1 thread-locally
Thread#3 :  counter.sum();       // == 1
Thread#1 :  counter.sum();       // == 3 (after merging everything)

If this is possible, AtomicLong is not really suitable for my use case, which probably then counts as "fine-grained synchronization control".

And then with my write/read^n pattern I probably can't do better then AtomicLong?

2 Answers
Related