AtomicLong运营

时间:2012-10-04 20:53:06

标签: java concurrency atomic atomic-long

我需要执行以下操作:

// average, total, elapsed are Long's

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

但我想使用AtomicLong

这就是我正在尝试但如果它是正确的我不会得到:

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

我怎么知道这是否正确?

4 个答案:

答案 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)

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