开发者

Java: is there no AtomicFloat or AtomicDouble?

开发者 https://www.devze.com 2023-02-21 09:55 出处:网络
I have found AtomicInteger, AtomicLong, but where is AtomicFloat (or AtomicDouble)? Maybe ther开发者_如何学JAVAe is some trick?The API docs for the java.util.concurrent package states the following:

I have found AtomicInteger, AtomicLong, but where is AtomicFloat (or AtomicDouble)? Maybe ther开发者_如何学JAVAe is some trick?


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

}


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


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?


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

}


It would be horrible inefficient to implement (but it would be possible). Per se its senseless to speak from atomic datatypes, because operations on datatypes are atomic, not the datatypes itself (maybe you know it, but just want to clear this point). With all this object stuff it gets mixed up. You need them very often in OS to manage locks and semaphores, thats why many processors have atomic integer instructions. For floats they are usually not implemented, so they get implemented, by wrapping the float operation in a block protected by a semaphore (which is implemented with atomic ints).

In high level java its no problem to make this locking for floats yourself (and you are right, they could have implemented it), but for efficiency you must implement them with the low level asm, so its very practical if you provide for the high level java folks some function that utilizes the low level asm instructions.

In reality I saw very seldom applications where atomic float operations are useful. I came across them, but very rare and it was always possible to reformulate the problem that the concurrency did not happen on the float part.


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.


Are you sure you need it?

Atomic classes are designed primarily as building blocks for implementing non-blocking data structures and related infrastructure classes. The compareAndSet method is not a general replacement for locking. It applies only when critical updates for an object are confined to a single variable.

Here is an explanation of the problems that atomic variables were designed to solve.


I found a library with AtomicFloat.

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

For quick solution, maven dependency given below:

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


May be the example that use AtomicReferenceArray can let you thinking

public class AtomicReferenceArrayRunner {

static Float[] ovalue = new Float[]{1.0f,2.0f};
static AtomicReferenceArray<Float> objarray = new AtomicReferenceArray(ovalue);

public static void main(String[] args) {
    System.out.println(objarray.get(0));
    objarray.set(0,3.0f);
    System.out.println(objarray.get(0));
}

} /** 1.0 3.0 */

0

精彩评论

暂无评论...
验证码 换一张
取 消