我需要执行以下操作:
// average, total, elapsed are Long's
average = ( ( total * average ) + elapsed ) / (++total);
但我想使用AtomicLong
这就是我正在尝试但如果它是正确的我不会得到:
average.set( (( total.get() * average.get() ) + elapsed) / total.getAndIncrement() );
我怎么知道这是否正确?
答案 0 :(得分:5)
据推测,您使用的是AtomicLong,因为这些数字是同时访问的。由于您涉及两个数字并且您在同一语句中同时使用get和incrementAndGet,我认为AtomicLong不是正确的选择。
我发现AtomicXXX在很多情况下非常有用。但在这里,我认为你需要以艰难的方式去做。使您的数字成为简单的“长”私有变量,创建一个保护对象,然后确保在您访问数字时同步保护对象。
我认为这是你可以确定这些操作真正原子化的唯一方法。
答案 1 :(得分:2)
首先请注意,在某些平台上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);
}
}
(免责声明:未经检查或测试或编译。)
答案 2 :(得分:1)
假设这些计算将由多个线程同时调用。我最初没有将其付诸实施。
如果您想使用AtomicLong
进行预增量计算,那么您应该执行以下操作:
long value = total.getAndIncrement();
average.set((value * average.get()) + elapsed) / (value + 1));
此仍然具有竞争条件,但由于average.get()
和average.set()
来电之间的其他人可能会更新平均值,这在更新中不会生效
为了完全确定,你需要(如他们的答案中提到的@ user1657364)锁定一个保护对象。
答案 3 :(得分:0)
在你的作业中,总数和总++中的总数可能不同。您需要同步整个操作。