web-dev-qa-db-fra.com

Java: n'y a-t-il pas AtomicFloat ou AtomicDouble?

J'ai trouvé AtomicInteger, AtomicLong, mais où est AtomicFloat (ou AtomicDouble)? Peut-être qu'il y a un truc?

38
itun

Vous pourriez peut-être utiliser un AtomicReference<Float> à la place. Je pense que AtomicInteger et AtomicLong ont des classes spéciales car elles sont utiles pour compter.

7
ColinD

Je suis également surpris qu'il n'y ait pas de solution intégrée. Le cas d'utilisation consiste à obtenir la somme en virgule flottante des valeurs émises par une collection de threads simultanés sans utiliser la mémoire en utilisant la mise à l'échelle avec le nombre de valeurs. Par exemple, les threads simultanés sont des moteurs de prédiction et vous souhaitez surveiller la somme des résidus prédits moins vérité de tous les moteurs de prédiction au même endroit. Des tentatives simultanées d’ajout à un compteur naïf entraîneraient une perte de compteurs (exactement de la même manière que des compteurs d’entiers).

Un ConcurrentLinkedQueue peut collecter les valeurs à additionner, mais à moins qu'un thread ne soit dédié à la réduction de cette file d'attente (en cours d'exécution result += q.poll() jusqu'à ce que le sondage renvoie null, puis q.add(result) et attendez un instant qu'il soit à nouveau rempli), la taille de la file d'attente deviendrait le nombre de valeurs à additionner.

Java 8 a DoubleAdder et Guava a AtomicDouble (voir les commentaires sur d'autres questions), mais cela n'aide pas les développeurs de bibliothèques qui ciblent l'ancien Java avec des dépendances minimales. J'ai consulté un échantillon de code DoubleAdder et code AtomicDouble , et ce que j'ai trouvé m'a surpris: ils réessaient simplement d'ajouter, suivi de compareAndSet, jusqu'à ce que ce ne soit pas erroné. Le nombre de threads qui tentent d'écrire peut augmenter en cas de conflit, mais s'ils ne sont pas parfaitement synchronisés, certains remporteront la course et s'éloigneront tandis que d'autres continueront de réessayer.

Voici une implémentation Scala de ce qu’ils font:

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
    }
}

et une tentative de traduction en 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();
        }
    }
}

Cela fonctionne (version de Scala testée avec des centaines de threads), et fournit un moyen de généraliser à partir de Double.

Cependant, je ne vois pas pourquoi cela serait plus rapide ou préférable à la synchronisation en écriture uniquement. Une solution de blocage ferait également attendre certains threads tandis que d’autres incrémenteraient le compteur, mais avec la garantie que tout finirait bien (pas de dépendance sur un minutage imparfait) ni de CPU gaspillé (ne calculez pas la somme tant que vous ne savez pas que vous êtes autorisé à le faire.) le mettre à jour). Alors pourquoi faire ça?

6
Jim Pivarski

Ce serait horriblement inefficace à mettre en œuvre (mais ce serait possible). En soi, il est insensé de parler de types de données atomiques, car les opérations sur les types de données sont atomiques, et non les types de données eux-mêmes (vous le savez peut-être, mais vous voulez simplement effacer ce point). Avec tout cet objet, ça se mélange. Vous en avez souvent besoin sous OS pour gérer les verrous et les sémaphores, raison pour laquelle de nombreux processeurs ont des instructions atomiques entières. Pour les floats, ils ne sont généralement pas implémentés. Ils sont donc implémentés, en encapsulant l'opération float dans un bloc protégé par un sémaphore (implémenté avec des entiers atomiques). 

En Java de haut niveau, ce n'est pas un problème de faire ce verrouillage pour les flotteurs (et vous avez raison, ils auraient pu l'implémenter), mais pour des raisons d'efficacité, vous devez les implémenter avec un ASM de bas niveau. niveau Java remplit certaines fonctions qui utilisent les instructions asm de bas niveau. 

En réalité, j'ai rarement vu des applications où les opérations de flottement atomique sont utiles. Je suis tombé sur eux, mais très rare et il était toujours possible de reformuler le problème que la concurrence ne se produisait pas sur la partie float.

1
flolo

Êtes-vous sûr d'en avoir besoin?

Les classes atomiques sont conçues principalement comme des blocs de construction pour la mise en œuvre de structures de données non bloquantes et de classes d'infrastructure associées. La méthode compareAndSet n'est pas un remplacement général pour le verrouillage. Il ne s'applique que lorsque les mises à jour critiques d'un objet sont limitées à une seule variable.

Ici est une explication des problèmes que les variables atomiques ont été conçues pour résoudre.

0
z7sg Ѫ

Ce n'est pas un problème de Java, toutes les langues en souffrent. 

Les instructions d’assemblage compilées par les opérations de comparaison et d’échange atomiques sont des variantes de: http://x86.renejeschke.de/html/file_module_x86_id_41.html

Celles-ci fonctionnent toutes sur des nombres entiers et la nature en pipeline de la FPU rend beaucoup plus difficile sa mise en œuvre pour les flotteurs/doubles. 

0
Stefano

J'ai trouvé une bibliothèque avec AtomicFloat.

http://dhale.github.io/jtk/api/edu/mines/jtk/util/AtomicFloat.html

Pour une solution rapide, dépendance maven donnée ci-dessous:

<dependency>
    <groupId>edu.mines.jtk</groupId>
    <artifactId>edu-mines-jtk</artifactId>
    <version>1.1.0</version>
</dependency>
0
Abishek Stephen

Bien que certaines des réponses ici certaines implémentation, aucune ne semble offrir un complet et complet

Celui-ci fait . C'est AtomicDouble et non AtomicFloat car il a une précision supérieure à float. 

Certaines des implémentations publiées ici, y compris Google Goava, ne disposent pas des fonctions de mise à jour. 

average.set( average.get() > x ? dosomething(y) : y) ; 

ne peut pas être effectué entièrement atomique. Celui-ci vous permet de faire: 

average.updateAndGet(new DoubleUnaryOperator() {                
    @Override
    public double applyAsDouble( double previous ) {
           return previous > x ? dosomething(y) : y; 
    }
});

Implémentation complète ci-dessous avec les mêmes méthodes que celles trouvées dans 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);
        }

}
0
momomo