6

我需要执行以下操作:

// average, total, elapsed are Long's

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

但我想用AtomicLong

这是我正在尝试的,但我不太明白它是否正确:

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

我如何判断这是否正确?

4

4 回答 4

6

大概您正在使用 AtomicLong 因为这些数字是同时访问的。由于您涉及两个数字,并且在同一语句中同时使用 get 和 incrementAndGet,我认为 AtomicLong 不是正确的选择。

我发现 AtomicXXX 在很多情况下都非常有用。但是在这里,我认为您需要以艰难的方式做到这一点。使您的数字成为简单的“长”私有变量,创建一个保护对象,然后确保在您访问数字时在保护对象上同步。

我认为这是您可以确定这些操作是真正原子的唯一方法。

于 2012-10-04T21:06:52.763 回答
2

首先请注意,在某些平台上AtomicLong是使用锁实现的,因此您可能会看到性能上的显着变化。

您似乎试图一次更新两个变量。尽管许多现代处理器都支持这一点,但 Java 库不支持。带锁的版本是微不足道的,所以我会省略它。您可能还想计算 get 的平均值,并保持运行总和和总计,但我暂时忽略它。

最直接的实现是使用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);
    }
}

(免责声明:未经检查、测试或编译。)

于 2012-10-04T21:19:38.797 回答
1

假设这些计算将由多个线程同时调用。我最初并没有将其付诸实施。

如果您想使用AtomicLong来进行预增量计算,那么您应该执行以下操作:

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

但是,这仍然存在竞争条件,因为平均值可以由其他人在average.get()average.set()调用之间更新,这不会在更新中生效。

为了完全确定,您需要(正如@user1657364 在他们的回答中提到的那样)锁定一个保护对象。

于 2012-10-04T20:57:44.150 回答
0

在您的作业中,第一个总计和总计++中的总计可能不同。您需要同步整个操作。

于 2014-01-05T01:34:45.673 回答