14

Is there any scenario in which AtomicInteger.accumulateAndGet() can't be replaced with AtomicInteger.updateAndGet(), or is it just a convenience for method references?

Here's a simple example where I don't see any functional difference:

AtomicInteger i = new AtomicInteger();
i.accumulateAndGet(5, Math::max);
i.updateAndGet(x -> Math.max(x, 5));

Obviously, the same goes for getAndUpdate() and getAndAccumulate().

shmosel
  • 49,289
  • 6
  • 73
  • 138
  • 2
    Isn’t the ability to (re)use existing functions (or non-capturing method references) a useful feature? There’s also no functional difference between `i.incrementAndGet()` and `i.accumulateAndGet(1, Integer::sum)`… – Holger Mar 15 '16 at 22:09
  • @Holger `accumulateAndGet()` was added later, plus it can't use `Unsafe`. `addAndGet(1)` might be a better example, but even that wasn't using `Unsafe` at the time it was created. I do, however, accept your general point; this question is just to clarify whether that was indeed the motivation. – shmosel Mar 15 '16 at 22:25

2 Answers2

10

When in doubt, you may look into implementation:

public final int accumulateAndGet(int x,
                                  IntBinaryOperator accumulatorFunction) {
    int prev, next;
    do {
        prev = get();
        next = accumulatorFunction.applyAsInt(prev, x);
    } while (!compareAndSet(prev, next));
    return next;
}

public final int updateAndGet(IntUnaryOperator updateFunction) {
    int prev, next;
    do {
        prev = get();
        next = updateFunction.applyAsInt(prev);
    } while (!compareAndSet(prev, next));
    return next;
}

They differ only in single line and obviously accumulateAndGet could be expressed easily via updateAndGet:

public final int accumulateAndGet(int x,
                                  IntBinaryOperator accumulatorFunction) {
    return updateAndGet(prev -> accumulatorFunction.applyAsInt(prev, x));
}

So updateAndGet is somewhat more basic operation and accumulateAndGet is a useful shortcut. Such shortcut might be especially helpful if your x is not effectively final:

int nextValue = 5;
if(something) nextValue = 6;
i.accumulateAndGet(nextValue, Math::max);
// i.updateAndGet(prev -> Math.max(prev, nextValue)); -- will not work
Tagir Valeev
  • 97,161
  • 19
  • 222
  • 334
4

There are cases where an instance creation can be avoided by using accumulateAndGet.

This is not really a functional difference but it might be useful to know about.

Consider the following example:

void increment(int incValue, AtomicInteger i) {
    // The lambda is closed over incValue. Because of this the created
    // IntUnaryOperator will have a field which contains incValue. 
    // Because of this a new instance must be allocated on every call
    // to the increment method.
    i.updateAndGet(value -> incValue + value);

    // The lambda is not closed over anything. The same
    // IntBinaryOperator instance can be used on every call to the 
    // increment method.
    //
    // It can be cached in a field, or maybe the optimizer is able 
    // to reuse it automatically.
    IntBinaryOperator accumulatorFunction =
            (incValueParam, value) -> incValueParam + value;

    i.accumulateAndGet(incValue, accumulatorFunction);
}

Instance creations are generally not expensive but can be important to get rid of in short operations that are used very frequently in performance sensitive locations.

More information about when lambda instances are reused can be found in this answer.

Lii
  • 11,553
  • 8
  • 64
  • 88