AtomicLong операции

Мне нужно выполнить следующую операцию:

// average, total, elapsed are Long's

average = ( ( total * average ) + elapsed ) / (++total);

Но я хочу использовать AtomicLong

Это то, что я пытаюсь, но я не совсем понимаю, если это правильно:

 average.set( (( total.get() * average.get() ) + elapsed) / total.getAndIncrement() );

Как я могу сказать, если это правильно?

4 ответа

Предположительно вы используете AtomicLong, потому что к этим номерам обращаются одновременно. Поскольку задействованы два числа, и вы используете и get, и incrementAndGet в одном выражении, я не думаю, что AtomicLong - это правильный выбор.

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

Я думаю, что это единственный способ убедиться, что эти операции действительно атомарны.

Во-первых, обратите внимание, что на некоторых платформах AtomicLong реализован с блокировкой, так что вы можете увидеть значительные различия в производительности.

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

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

final class Average { // Find a better name...
    private final long average;
    private final long total;
    public Average(long average, long total) {
        this.average = average
        this.total = total;
    }
    public long average() {
        return average;
    }
    public long total() {
        return total;
    }
}
...
private final AtomicReference<Average> averageRef = new AtomicReference<>();
private void elapsed(final long elapsed) {
    Average prev;
    Average next;
    do {
        prev = average.get();
        next = new Average(
            ((prev.total() * prev.average()) + elapsed ) / (prev.total() + 1),
            prev.total() + 1
        );
    } while (!average.compareAndSet(prev, next));
}

Возможно, лучшим решением будет сохранить локальный поток (желательно не ThreadLocal но экземпляр, который вы дали конкретному потоку для изменения). Это может быть очень быстро заблокировано и разблокировано, потому что это будет из того же потока. Поток, редко требующий среднего, может затем заблокировать и прочитать / прочитать текущие значения из всех потоков.

class Average { // Choose better name
    private long sum;
    private long total;
    public synchronized void elapsed(final long elapsed) {
         sum += elapsed;
         ++total;
    }
    public static long average(Iterable<Average> averages) {
        long sum = 0;
        long total = 0;
        for (Average average : averages) {
            synchronized (average) {
                sum += averages.sum;
                total += average.total;
            }
        }
        return total==0 ? 0 : (sum/total);
    }
}

(Отказ от ответственности: не проверено, не проверено и не скомпилировано.)

Предполагается, что эти вычисления будут вызываться несколькими потоками одновременно. Я изначально не принял это в действие.

Если вы хотите использовать AtomicLong чтобы сделать предварительный расчет, вам нужно сделать что-то вроде:

long value = total.getAndIncrement();
average.set((value * average.get()) + elapsed) / (value + 1));

Это все еще имеет условия гонки, однако, так как среднее может быть обновлено кем-то еще между average.get() и average.set() вызов, который не будет принят в силу в обновлении.

Чтобы быть полностью уверенным, вам нужно (как @user1657364 упомянул в своем ответе) заблокировать объект защиты.

В вашем задании итоговое значение может отличаться от первого итога и итого ++. Вам нужно синхронизировать всю операцию.

Другие вопросы по тегам