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 を使用していると思われます。2 つの数値が関係しており、同じステートメントで get と incrementAndGet の両方を使用しているため、AtomicLong は正しい選択ではないと思います。

AtomicXXX は多くの状況で非常に役立つことがわかりました。しかし、ここでは、難しい方法で行う必要があると思います。数値を単純な「長い」プライベート変数にして、ガード オブジェクトを作成し、数値にアクセスするたびにガード オブジェクトで同期するようにします。

これが、これらの操作が本当にアトミックであることを確認できる唯一の方法だと思います。

于 2012-10-04T21:06:52.763 に答える
2

最初に、一部のプラットフォームAtomicLongでは がロック付きで実装されているため、パフォーマンスに大きなばらつきが見られる場合があることに注意してください。

一度に 2 つの変数を更新しようとしているようです。最近のプロセッサの多くはこれをサポートしていますが、Java ライブラリはサポートしていません。ロック付きのバージョンは些細なことなので省略します。また、取得時に平均を計算して、現在の合計と合計を保持することもできますが、ここではそれを無視します。

最も文字通りの実装は、AtomicReferenceto を不変の値に使用することです。これにより割り当てが発生するため、特に競合が少ない場合は、優れたパフォーマンスが得られる可能性があることに注意してください。

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 に答える