Есть ли какая-либо функциональная разница между AtomicInteger.updateAndGet() и AtomicInteger.accumulateAndGet()?

Есть ли сценарий, в котором AtomicInteger.accumulateAndGet() не может быть заменен на AtomicInteger.updateAndGet(), или это просто удобство для ссылок на методы?

Вот простой пример, где я не вижу функциональной разницы:

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

Очевидно, то же самое происходит и для getAndUpdate() и getAndAccumulate().

Ответ 1

Если вы сомневаетесь, вы можете посмотреть на реализацию:

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;
}

Они отличаются только одной строкой и, очевидно, accumulateAndGet updateAndGet можно легко выразить через updateAndGet:

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

Таким образом, updateAndGet - это несколько более простая операция, а accumulateAndGet updateAndGet - полезный ярлык. Такой ярлык может быть особенно полезен, если ваш x не является окончательным:

int nextValue = 5;
if(something) nextValue = 6;
i.accumulateAndGet(nextValue, Math::max);
// i.updateAndGet(prev -> Math.max(prev, nextValue)); -- will not work

Ответ 2

Существуют случаи, когда можно избежать создания экземпляра с помощью accumulateAndGet.

На самом деле это не функциональная разница, но о ней полезно знать.

Рассмотрим следующий пример:

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);
}

Создание экземпляров, как правило, не дорого, но может быть важно, чтобы избавиться от коротких операций, которые очень часто используются в чувствительных к производительности местах.

Более подробную информацию о том, когда лямбда-экземпляры используются повторно, можно найти в этом ответе.