AtomicInteger
、AtomicLong
は見つかりましたが、AtomicFloat
(またはAtomicDouble
)はどこにありますか?多分いくつかのトリックがありますか?
Java.util.concurrent
パッケージ のAPIドキュメントには次のように記載されています。
[...]さらに、クラスは、意図されたアプリケーションで一般的に役立つタイプに対してのみ提供されます。たとえば、バイトを表すためのアトミッククラスはありません。そうしたい場合がまれにある場合は、
AtomicInteger
を使用してバイト値を保持し、適切にキャストできます。Float.floatToIntBits
およびFloat.intBitstoFloat
変換を使用して浮動小数点数を保持したり、Double.doubleToLongBits
およびDouble.longBitsToDouble
変換を使用して倍数を保持したりすることもできます。
私はそれが便利な解決策であると主張していませんが、それは説明のようです。おそらくAtomicInteger
をラップして、getFloat
/setFloat
などへのアクセスメソッドを提供したいと思うでしょう。
実際に書いてみました。どうぞ:
import Java.util.concurrent.atomic.AtomicInteger;
import static Java.lang.Float.*;
class AtomicFloat extends Number {
private AtomicInteger bits;
public AtomicFloat() {
this(0f);
}
public AtomicFloat(float initialValue) {
bits = new AtomicInteger(floatToIntBits(initialValue));
}
public final boolean compareAndSet(float expect, float update) {
return bits.compareAndSet(floatToIntBits(expect),
floatToIntBits(update));
}
public final void set(float newValue) {
bits.set(floatToIntBits(newValue));
}
public final float get() {
return intBitsToFloat(bits.get());
}
public float floatValue() {
return get();
}
public final float getAndSet(float newValue) {
return intBitsToFloat(bits.getAndSet(floatToIntBits(newValue)));
}
public final boolean weakCompareAndSet(float expect, float update) {
return bits.weakCompareAndSet(floatToIntBits(expect),
floatToIntBits(update));
}
public double doubleValue() { return (double) floatValue(); }
public int intValue() { return (int) get(); }
public long longValue() { return (long) get(); }
}
おそらくAtomicReference<Float>
代わりに。 AtomicInteger
とAtomicLong
は、カウントに役立つため、特別なクラスを取得すると思います。
また、組み込みのソリューションがないことにも驚いています。 ユースケースは、値の数によるメモリ使用スケーリングなしで、同時スレッドのコレクションによって発行された値の浮動小数点合計を取得することです。たとえば、同時スレッドは予測エンジンであり、すべての予測エンジンからの予測されたマイナスの残差の合計を1か所で監視したいとします。単純なカウンターに同時に追加しようとすると、カウントが失われます(整数カウンターとまったく同じ方法)。
ConcurrentLinkedQueue
は合計する値を収集できますが、そのキューを減らすための専用スレッドがない限り(ポーリングがnull
を返すまでresult += q.poll()
を常に実行し、その後q.add(result)
し、再び一杯になるまでしばらく待ちます)、キューのサイズは合計する値の数まで増加します。
Java 8にはDoubleAdder
があり、GuavaにはAtomicDouble
があります(他の質問のコメントを参照)。ただし、これは、依存関係が最小限の古いJavaをターゲットとするライブラリ開発者には役立ちません。私は DoubleAdder code と AtomicDouble code のサンプルを見て、驚いた:彼らが追加を再試行し、その後compareAndSet
が再試行されるまでエラーが発生しました。競合が発生している間、書き込みを試みるスレッドの数は増加する可能性がありますが、完全なロックステップでない限り、競合に勝利して邪魔にならないものもあれば、再試行を続けるものもあります。
ここでは、Scalaの実装を示します。
class AtomicDouble {
private val value = new AtomicReference(Java.lang.Double.valueOf(0.0))
@tailrec
final def getAndAdd(delta: Double): Double = {
val currentValue = value.get
val newValue = Java.lang.Double.valueOf(currentValue.doubleValue + delta)
if (value.compareAndSet(currentValue, newValue))
currentValue.doubleValue
else
getAndAdd(delta) // try, try again
}
}
試行されたJava翻訳:
class AtomicDouble {
private AtomicReference<Double> value = new AtomicReference(Double.valueOf(0.0));
double getAndAdd(double delta) {
while (true) {
Double currentValue = value.get();
Double newValue = Double.valueOf(currentValue.doubleValue() + delta);
if (value.compareAndSet(currentValue, newValue))
return currentValue.doubleValue();
}
}
}
これは機能し(数百のスレッドでテストされたScalaバージョン)、Double
から一般化する方法を提供します。
ただし、これが書き込み専用の同期よりも高速または優先される理由はわかりません。ブロッキングソリューションでは、一部のスレッドが待機している間、他のスレッドがカウンターをインクリメントしますが、すべてのスレッドが最終的に完了し(不完全なタイミングに依存せず)、無駄なCPUがないことを保証します(許可されるまで合計を計算しないでください)。更新してください)。なぜこれを行うのですか?
実装することは恐ろしく非効率的です(しかし、それは可能です)。データ型に対する操作はアトミックであり、データ型自体ではないので、それ自体はアトミックデータ型から話すのは無意味です(多分あなたはそれを知っているかもしれませんが、この点を明らかにしたいだけです)。このすべてのオブジェクトは混同されます。ロックやセマフォを管理するためにOSで非常に頻繁にそれらを必要とするため、多くのプロセッサがアトミック整数命令を持っています。フロートの場合、通常は実装されないため、セマフォ(アトミックintで実装されている)で保護されたブロックにフロート操作をラップすることで実装されます。
高レベルではJavaフロートを自分でロックすることは問題ありません(そして、あなたが正しい、彼らはそれを実装できたでしょう)が、効率のために低レベルのasmでそれらを実装する必要があるため、高水準のJava=人々が低水準のasm命令を利用するいくつかの関数を提供する場合、それは非常に実用的です。
実際には、アトミックfloat演算が役立つアプリケーションはほとんどありませんでした。私はそれらに出くわしましたが、非常にまれであり、フロート部分で並行性が発生しないという問題を再定式化することは常に可能でした。
本当に必要ですか?
アトミッククラスは主に、非ブロッキングデータ構造と関連するインフラストラクチャクラスを実装するためのビルディングブロックとして設計されています。 compareAndSetメソッドは、ロックの一般的な置き換えではありません。これは、オブジェクトの重要な更新が単一の変数に限定されている場合にのみ適用されます。
ここ は、原子変数が解決するように設計された問題の説明です。
これはJava=の問題ではありません。すべての言語でこの問題が発生しています。
アトミック比較およびスワップ操作がコンパイルされるアセンブリ命令は、以下のバリアントです。 http://x86.renejeschke.de/html/file_module_x86_id_41.html
これらはすべて整数で動作し、FPUのパイプライン化された性質により、float/doubleの実装がはるかに困難になります。
AtomicFloatのライブラリを見つけました。
http://dhale.github.io/jtk/api/edu/mines/jtk/util/AtomicFloat.html
迅速な解決策として、Mavenの依存関係を以下に示します。
<dependency>
<groupId>edu.mines.jtk</groupId>
<artifactId>edu-mines-jtk</artifactId>
<version>1.1.0</version>
</dependency>
ここでの回答の一部はsome実装ですが、-完全かつ完全なものを提供するものはありません。
これはです。 floatよりも精度が高いため、AtomicFloatではなく、AtomicDoubleです。
グーグルグアバを含む、ここに掲載された実装の一部には、更新機能がないため、次のような操作があります。
average.set( average.get() > x ? dosomething(y) : y) ;
完全にアトミックに実行することはできません。これにより、次のことが可能になります。
average.updateAndGet(new DoubleUnaryOperator() {
@Override
public double applyAsDouble( double previous ) {
return previous > x ? dosomething(y) : y;
}
});
AtomicLongにあるのと同じメソッドを使用した以下の完全な実装:
import static Java.lang.Double.doubleToLongBits;
import static Java.lang.Double.longBitsToDouble;
import Java.util.concurrent.atomic.AtomicLong;
import Java.util.function.DoubleBinaryOperator;
import Java.util.function.DoubleUnaryOperator;
public final class AtomicDouble extends Number {
private static final long serialVersionUID = 12327722191124184L;
private final AtomicLong bits;
public AtomicDouble() {
this(0.0d);
}
public AtomicDouble( double initialValue ) {
bits = new AtomicLong( toLong(initialValue) );
}
/**
* Atomically sets the value to the given updated value
* if the current value {@code ==} the expected value.
*
* @param expect the expected value
* @param update the new value
* @return {@code true} if successful. False return indicates that
* the actual value was not equal to the expected value.
*/
public final boolean compareAndSet( double expect, double update ) {
return bits.compareAndSet(toLong(expect), toLong(update));
}
/**
* Sets to the given value.
*
* @param newValue the new value
*/
public final void set( double newValue ) {
bits.set(toLong(newValue));
}
public final double get() {
return toDouble(bits.get());
}
/**
* Atomically sets to the given value and returns the old value.
*
* @param newValue the new value
* @return the previous value
*/
public final double getAndSet( double newValue ) {
return toDouble( bits.getAndSet(toLong(newValue)) );
}
/**
* Atomically sets the value to the given updated value
* if the current value {@code ==} the expected value.
*
* <p><a href="package-summary.html#weakCompareAndSet">May fail
* spuriously and does not provide ordering guarantees</a>, so is
* only rarely an appropriate alternative to {@code compareAndSet}.
*
* @param expect the expected value
* @param update the new value
* @return {@code true} if successful
*/
public final boolean weakCompareAndSet( double expect, double update ) {
return bits.weakCompareAndSet(toLong(expect), toLong(update));
}
/**
* Atomically updates the current value with the results of
* applying the given function to the current and given values,
* returning the updated value. The function should be
* side-effect-free, since it may be re-applied when attempted
* updates fail due to contention among threads. The function
* is applied with the current value as its first argument,
* and the given update as the second argument.
*
* @param x the update value
* @param accumulatorFunction a side-effect-free function of two arguments
* @return the updated value
* @since 1.8
*/
public final double accumulateAndGet( double x, DoubleBinaryOperator accumulatorFunction ) {
double prev, next;
do {
prev = get();
next = accumulatorFunction.applyAsDouble(prev, x);
} while (!compareAndSet(prev, next));
return next;
}
/**
* Atomically adds the given value to the current value.
*
* @param delta the value to add
* @return the updated value
*/
public final double addAndGet( double delta ) {
return toDouble(bits.addAndGet(toLong(delta)));
}
/**
* Atomically decrements by one the current value.
*
* @return the updated value
*/
public final double decrementAndGet() {
return addAndGet(-1.0d);
}
/**
* Atomically updates the current value with the results of
* applying the given function to the current and given values,
* returning the previous value. The function should be
* side-effect-free, since it may be re-applied when attempted
* updates fail due to contention among threads. The function
* is applied with the current value as its first argument,
* and the given update as the second argument.
*
* @param x the update value
* @param accumulatorFunction a side-effect-free function of two arguments
* @return the previous value
* @since 1.8
*/
public final double getAndAccumulate( double x, DoubleBinaryOperator accumulatorFunction ) {
double prev, next;
do {
prev = get();
next = accumulatorFunction.applyAsDouble(prev, x);
} while (!compareAndSet(prev, next));
return prev;
}
/**
* Atomically adds the given value to the current value.
*
* @param delta the value to add
* @return the previous value
*/
public final double getAndAdd( double delta ) {
return toDouble(bits.getAndAdd(toLong(delta)));
}
public final double getAndDecrement() {
return getAndAdd(-1.0d);
}
/**
* Atomically increments by one the current value.
*
* @return the previous value
*/
public final double getAndIncrement() {
return getAndAdd(1.0d);
}
/**
* Atomically increments by one the current value.
*
* @return the updated value
*/
public final double incrementAndGet() {
return addAndGet(1.0d);
}
/**
* Atomically updates the current value with the results of
* applying the given function, returning the previous value. The
* function should be side-effect-free, since it may be re-applied
* when attempted updates fail due to contention among threads.
*
* @param updateFunction a side-effect-free function
* @return the previous value
* @since 1.8
*/
public final double getAndUpdate( DoubleUnaryOperator updateFunction ) {
double prev, next;
do {
prev = get();
next = updateFunction.applyAsDouble(prev);
} while (!compareAndSet(prev, next));
return prev;
}
/**
* Eventually sets to the given value.
*
* @param newValue the new value
* @since 1.6
*/
public final void lazySet( double newValue ) {
bits.lazySet(toLong(newValue));
// unsafe.putOrderedLong(this, valueOffset, newValue);
}
/**
* Returns the value of this {@code AtomicLong} as a {@code long}.
*/
public long longValue() {
return (long) get();
}
/**
* Returns the String representation of the current value.
*
* @return the String representation of the current value
*/
public String toString() {
return Double.toString(get());
}
/**
* Atomically updates the current value with the results of
* applying the given function, returning the updated value. The
* function should be side-effect-free, since it may be re-applied
* when attempted updates fail due to contention among threads.
*
* @param updateFunction a side-effect-free function
* @return the updated value
* @since 1.8
*/
public final double updateAndGet( DoubleUnaryOperator updateFunction ) {
double prev, next;
do {
prev = get();
next = updateFunction.applyAsDouble(prev);
} while (!compareAndSet(prev, next));
return next;
}
/**
* Returns the value of this {@code AtomicLong} as an {@code int}
* after a narrowing primitive conversion.
*
* @jls 5.1.3 Narrowing Primitive Conversions
*/
public int intValue() {
return (int) get();
}
/**
* Returns the value of this {@code AtomicLong} as a {@code float}
* after a widening primitive conversion.
*
* @jls 5.1.2 Widening Primitive Conversions
*/
public float floatValue() {
return (float) get();
}
/**
* Returns the value of this {@code AtomicLong} as a {@code double}
* after a widening primitive conversion.
*
* @jls 5.1.2 Widening Primitive Conversions
*/
public double doubleValue() {
return get();
}
private static double toDouble( long l ) {
return longBitsToDouble(l);
}
private static long toLong( double delta ) {
return doubleToLongBits(delta);
}
}