【问题标题】:AtomicLong operationsAtomicLong 操作
【发布时间】:2012-10-04 20:53:05
【问题描述】:

我需要执行以下操作:

// average, total, elapsed are Long's

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

但我想用AtomicLong

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

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

如何判断这是否正确?

【问题讨论】:

  • 你正在做的是... / (total++)。那是你要的吗?即,您在示例操作中增加了 AtomicLong 而不是 total
  • 哦,是的,total++ 这就是我想要的。嗯,我想我实际上需要 ++total。
  • 那你要total.incrementAndGet()
  • 在这种情况下,您不应该在同步块中执行此操作吗?

标签: java concurrency atomic atomic-long


【解决方案1】:

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

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

我认为只有这样才能确定这些操作是真正的原子操作。

【讨论】:

    【解决方案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);
        }
    }
    

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

    【讨论】:

    • 我还想知道我有多少并发用户:simultaneous.set(simultaneous.incrementAndGet()); hanlde(newThreadEtc); simultaneous.set(simultaneous.decrementAndGet()); 那行得通还是我也应该走很长的路?
    【解决方案3】:

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

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

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

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

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

    【讨论】:

      【解决方案4】:

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

      【讨论】:

        猜你喜欢
        • 2016-03-15
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-08-21
        • 1970-01-01
        • 2016-06-11
        • 1970-01-01
        相关资源
        最近更新 更多