Java: is there no AtomicFloat or AtomicDouble?

38,756

Solution 1

The API docs for the java.util.concurrent package states the following:

[...] Additionally, classes are provided only for those types that are commonly useful in intended applications. For example, there is no atomic class for representing byte. In those infrequent cases where you would like to do so, you can use an AtomicInteger to hold byte values, and cast appropriately. You can also hold floats using Float.floatToIntBits and Float.intBitstoFloat conversions, and doubles using Double.doubleToLongBits and Double.longBitsToDouble conversions.

I'm not claiming it's a convenient solution, but that seems to be the explanation. I suppose you would probably want to wrap an AtomicInteger and provide access methods for getFloat / setFloat etc.


I actually got around writing one. Here you go:

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();          }

}

Solution 2

You could perhaps use an AtomicReference<Float> instead. I think AtomicInteger and AtomicLong get special classes because they're useful for counting.

Solution 3

I'm also surprised there wasn't a built-in solution. The use-case is to get the floating-point sum of values emitted by a collection of concurrent threads without memory use scaling with the number of values. For instance, the concurrent threads are prediction engines and you want to monitor the sum of predicted-minus-truth residuals from all prediction engines in one place. Simultaneous attempts to add to a naive counter would result in lost counts (in exactly the same way as integer counters).

A ConcurrentLinkedQueue can collect the values to sum, but unless there's a thread dedicated to reducing that queue (constantly running result += q.poll() until poll returns null, then q.add(result) and wait a moment for it to fill up again), the size of the queue would grow to the number of values to sum.

Java 8 has DoubleAdder and Guava has AtomicDouble (see comments on other questions), but that doesn't help library developers targeting old Java with minimal dependencies. I looked at a sample of DoubleAdder code and AtomicDouble code, and what I found surprised me: they just retry addition followed by compareAndSet until doing so is not erroneous. The number of threads attempting to write can increase while there's contention, but unless they're in perfect lock-step, some will win the race and get out of the way while others keep retrying.

Here's a Scala implementation of what they do:

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

and an attempted Java translation:

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();
        }
    }
}

It works (Scala version tested with hundreds of threads), and provides a way to generalize from Double.

However, I don't see any reason why this would be faster or preferred over synchronizing on write only. A blocking solution would also make some threads wait while others increment the counter, but with a guarantee that all will eventually finish (no reliance on imperfect timing) and no wasted CPU (don't compute the sum until you know you're allowed to update it). So why do this?

Solution 4

Although some of the answers here some implementation none seem to offer a full and complete one.

This one does. It's AtomicDouble and not AtomicFloat as it has higher precision than float.

As some of the implementations posted here, including the google guava they lack updater functions, so operations such as:

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

can not be performed fully atomic. This one allows you to do:

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

Full implementation below with same methods as found in 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);
        }

}

Solution 5

It's not a java issue, all languages suffer from this.

The assembly instructions which the atomic compare and swap operations compile down to are variants of: http://x86.renejeschke.de/html/file_module_x86_id_41.html

These all operate on integers and the pipelined nature of the FPU makes it much harder to implement for floats/doubles.

Share:
38,756
itun
Author by

itun

Updated on June 30, 2021

Comments

  • itun
    itun almost 3 years

    I have found AtomicInteger, AtomicLong, but where is AtomicFloat (or AtomicDouble)? Maybe there is some trick?

  • aioobe
    aioobe about 13 years
    Are you sure you need it? -- Perhaps he's just curious :-) I think it's a perfectly legitimate question to ask.
  • z7sg Ѫ
    z7sg Ѫ about 13 years
    @aioobe Yes but I just think that it's better to read about why AtomicInteger exists than provide a solution that probably isn't really needed.
  • Piotr Findeisen
    Piotr Findeisen about 13 years
    AtomicReference.compareAndSet compares by identity and not by equality, so it's no replacement for hypothetical AtomicFloat.
  • codeplay
    codeplay over 10 years
  • Jim Pivarski
    Jim Pivarski over 8 years
    This is missing the one feature that would be useful: addAndGet (or getAndAdd; doesn't matter which). Guava AtomicDouble and Java 8 DoubleAdder have it. All these questions about the use-case: to accumulate a sum of residuals coming from different threads, naturally!
  • aioobe
    aioobe over 8 years
    @JimPivarski, addAndGet can be implemented the same way getAndSet is implemented. Just go via the bits of the backing AtomicInteger.
  • Jim Pivarski
    Jim Pivarski over 8 years
    @aioobe Would that be atomic? If you (1) convert long bits to double, (2) add delta to that double, and (3) put the new double into the long bits, wouldn't it be possible for another thread to set the variable between steps (1-2) or (2-3), making the result of the addition invalid? I kind of don't even care about the 'AndGet' part, just commutative and associative addition. (I wrote an alternate answer below that maybe clarifies this point.)
  • TomWolk
    TomWolk almost 7 years
    Synchronization is very expensive. In the time needed to suspend and wake up a thread you could run the code in the while loop a couple of thousand times.
  • mjs
    mjs over 6 years
    @codeplay google guava lacks updateAndGet(lambda) :( This implementation too.
  • mjs
    mjs over 6 years
    I am working on a full implementation, but just wondering. Theoretically, aren't some of these technically not fully atomic? For instance, getAndSet takes floatValue, which has to convert it to int bits which at that at that point, theoretically another thread could swoop in and change the value, and then in the next second we would overwrite it ? We would get the wrong value back. For compare that would return false. Maybe minor. Google is handling this using an AtomicRefernce it seems. Might be "the best". I'll use that to provide a full implementation with all the goodies in AtomicLong.
  • aioobe
    aioobe over 6 years
    @JimPivarski, you're right. It would probably require additional locking.
  • aioobe
    aioobe over 6 years
    @momomo, you're thinking of it the wrong way. Suppose you used an AtomicInteger directly, and did the conversions on the outside. You would have a "thread safe" program. This code achieves the same thing. All concurrency-critical parts are done inside AtomicInteger.
  • noamik
    noamik over 5 years
    In case someone doesn't believe what @TomWolk said, just create both solutions and test them with openjdk.java.net/projects/code-tools/jmh The optimistic write approach will perform better if many concurrent writes occur.