example - atomicinteger java
Java: ¿no hay AtomicFloat o AtomicDouble? (7)
¿Estás seguro de que lo necesitas?
Las clases atómicas están diseñadas principalmente como bloques de construcción para implementar estructuras de datos no bloqueantes y clases de infraestructura relacionadas. El método compareAndSet no es un reemplazo general para el bloqueo. Se aplica solo cuando las actualizaciones críticas para un objeto están confinadas a una única variable.
Here hay una explicación de los problemas que las variables atómicas fueron diseñadas para resolver.
He encontrado AtomicInteger
, AtomicLong
, pero ¿dónde está AtomicFloat
(o AtomicDouble
)? Tal vez hay algún truco?
Aunque algunas de las respuestas aquí algunas implementaciones ninguna parecen ofrecer una completa y completa .
Este lo hace Es AtomicDouble y no AtomicFloat ya que tiene mayor precisión que float.
Como algunas de las implementaciones publicadas aquí, incluida la google guava, carecen de funciones de actualización, por lo que las operaciones como:
average.set( average.get() > x ? dosomething(y) : y) ;
no se puede realizar completamente atómico. Este te permite hacer:
average.updateAndGet(new DoubleUnaryOperator() {
@Override
public double applyAsDouble( double previous ) {
return previous > x ? dosomething(y) : y;
}
});
Implementación completa a continuación con los mismos métodos que se encuentran en 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);
}
}
En su lugar, podrías usar AtomicReference<Float>
. Creo que AtomicInteger
y AtomicLong
obtienen clases especiales porque son útiles para contar.
Los documentos API para el paquete java.util.concurrent
establecen lo siguiente:
[...] Además, las clases se proporcionan solo para aquellos tipos que son comúnmente útiles en las aplicaciones previstas. Por ejemplo, no existe una clase atómica para representar byte. En los casos poco frecuentes en los que le gustaría hacerlo, puede usar un
AtomicInteger
para mantener los valores de byte yAtomicInteger
adecuadamente. También puede mantener flotantes usando las conversionesFloat.floatToIntBits
yFloat.intBitstoFloat
, y se duplica usandoDouble.doubleToLongBits
yDouble.longBitsToDouble
.
No estoy afirmando que sea una solución conveniente, pero esa parece ser la explicación. Supongo que probablemente quieras envolver un AtomicInteger
y proporcionar métodos de acceso para getFloat
/ setFloat
etc.
De hecho, escribí uno. Aqui tienes:
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(); }
}
No es un problema de Java, todos los idiomas sufren de esto.
Las instrucciones de ensamblaje a las que se comparan las operaciones de comparación y intercambio atómicas son variantes de: http://x86.renejeschke.de/html/file_module_x86_id_41.html
Todos estos funcionan en enteros y la naturaleza segmentada de la FPU hace que sea mucho más difícil de implementar para flotantes / dobles.
Sería terriblemente ineficiente de implementar (pero sería posible). Por sí mismo no tiene sentido hablar de tipos de datos atómicos, porque las operaciones con tipos de datos son atómicas, no los tipos de datos en sí (tal vez usted lo sepa, pero solo quiere aclarar este punto). Con todo este objeto se confunde. Los necesita con mucha frecuencia en el sistema operativo para administrar bloqueos y semáforos, por eso muchos procesadores tienen instrucciones enteras atómicas. Para los flotadores, generalmente no se implementan, por lo que se implementan, envolviendo la operación de flotación en un bloque protegido por un semáforo (que se implementa con operaciones atómicas).
En java de alto nivel no hay problema para hacer que este bloqueo flote por ti mismo (y tienes razón, podrían haberlo implementado), pero para eficiencia debes implementarlo con el nivel bajo asm, por lo que es muy práctico si proporcionas el alto nivel java gente alguna función que utiliza las instrucciones bajo nivel de asm.
En realidad, vi muy pocas aplicaciones donde las operaciones de flotación atómica son útiles. Me encontré con ellos, pero muy raro y siempre fue posible reformular el problema de que la concurrencia no sucedió en la parte flotante.
También me sorprende que no haya una solución integrada. El caso de uso es obtener la suma de valores de punto flotante emitida por una colección de subprocesos concurrentes sin que la memoria use escala con la cantidad de valores. Por ejemplo, los subprocesos simultáneos son motores de predicción y desea controlar la suma de residuos de verdad pronosticados menos verdad de todos los motores de predicción en un solo lugar. Los intentos simultáneos de agregar a un contador ingenuo daría como resultado recuentos perdidos (exactamente de la misma manera que los contadores de enteros).
Un ConcurrentLinkedQueue
puede recopilar los valores en suma, pero a menos que haya un hilo dedicado a reducir esa cola (ejecutando constantemente result += q.poll()
hasta que poll devuelva null
, luego q.add(result)
y espere un momento para que se complete de nuevo), el tamaño de la cola aumentaría a la cantidad de valores a sumar.
Java 8 tiene DoubleAdder
y Guava tiene AtomicDouble
(consulte los comentarios de otras preguntas), pero eso no ayuda a los desarrolladores de bibliotecas a apuntar a Java antiguo con dependencias mínimas. Miré una muestra de código DoubleAdder y código AtomicDouble , y lo que encontré me sorprendió: simplemente vuelven a intentar la adición seguida por compareAndSet
hasta que hacerlo no sea erróneo. El número de subprocesos que intentan escribir puede aumentar mientras hay contienda, pero a menos que estén en un paso de bloqueo perfecto, algunos ganarán la carrera y se apartarán mientras otros continúan reintentando.
Aquí hay una implementación de Scala de lo que hacen:
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
}
}
y una tentativa de traducción de 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();
}
}
}
Funciona (la versión de Scala se probó con cientos de subprocesos) y proporciona una forma de generalizar desde Double
.
Sin embargo, no veo ninguna razón por la cual esto sea más rápido o preferible a la sincronización en solo escritura. Una solución de bloqueo también haría que algunos subprocesos esperen mientras que otros incrementan el contador, pero con la garantía de que todos terminarán finalmente (sin depender de un tiempo imperfecto) y no se desperdiciará la CPU (no calcule la suma hasta que sepa que puede hacerlo). actualizarla). Entonces, ¿por qué hacer esto?