Java: нет AtomicFloat или AtomicDouble?

36

Я нашел AtomicInteger, AtomicLong, но где AtomicFloat (или AtomicDouble)? Может быть, есть какой-то трюк?

  • 1
    Там нет ни одного. Какой у вас вариант использования?
  • 2
    DoubleAdder, добавленный в Java 8, может соответствовать вашим потребностям.
Теги:
atomic
concurrency

8 ответов

66
Лучший ответ

В документах API для java.util.concurrent package указано следующее:

[...] Кроме того, классы предоставляются только для тех типов, которые обычно полезны в предполагаемых приложениях. Например, для представления байта нет атомного класса. В тех редких случаях, когда вы хотели бы сделать это, вы можете использовать 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();          }

}
  • 11
    Также можно использовать AtomicDouble в Guava docs.guava-libraries.googlecode.com/git-history/v11.0.2/javadoc/…
  • 0
    В нем отсутствует одна полезная addAndGet : addAndGet (или getAndAdd ; не важно, какая). У Guava AtomicDouble и Java 8 DoubleAdder это есть. Все эти вопросы о сценарии использования: естественно, для накопления суммы остатков, поступающих из разных потоков!
Показать ещё 6 комментариев
8

Вместо этого вы можете использовать AtomicReference<Float>. Я думаю, что AtomicInteger и AtomicLong получают специальные классы, потому что они полезны для подсчета.

  • 12
    AtomicReference.compareAndSet сравнивает по идентичности, а не по равенству, поэтому он не заменяет гипотетический AtomicFloat .
6

Я также удивлен, что не было встроенного решения. Предполагается, что сумма значений значений, испускаемых коллекцией параллельных потоков без использования памяти, с количеством значений будет равна сумме значений с плавающей запятой. Например, параллельные потоки являются механизмами прогнозирования и вы хотите контролировать сумму остатков предсказанной минусовой истины от всех двигателей прогнозирования в одном месте. Одновременные попытки добавить к наивному счетчику приведут к потере отсчетов (точно так же, как и целые счетчики).

A ConcurrentLinkedQueue может собирать значения для суммы, но если нет потока, посвященного сокращению этой очереди (постоянно работает result += q.poll() до тех пор, пока опрос не вернет null), тогда q.add(result) и подождите, пока он не заполнит снова), размер очереди увеличился бы до количества значений, которые будут суммироваться.

Java 8 имеет DoubleAdder и Guava имеет AtomicDouble (см. комментарии по другим вопросам), но это не помогает разработчикам библиотек, ориентированным на старую Java с минимальными зависимостями. Я рассмотрел образец Код DoubleAdder и Код AtomicDouble, и то, что я обнаружил, удивил меня: они просто повторили добавление, а затем 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 (не вычисляйте сумму, пока не узнаете, что вам разрешено обновите его). Так зачем это делать?

  • 0
    Синхронизация очень дорогая. За время, необходимое для приостановки и пробуждения потока, вы можете выполнить код в цикле while пару тысяч раз.
  • 0
    В случае, если кто-то не верит тому, что сказал @TomWolk, просто создайте оба решения и протестируйте их с openjdk.java.net/projects/code-tools/jmh Оптимистический подход к записи будет работать лучше, если произойдет много одновременных записей .
1

Было бы ужасно неэффективно реализовывать (но это было бы возможно). По сути, бессмысленно говорить из атомных типов данных, поскольку операции с типами данных являются атомарными, а не самими типами данных (возможно, вы это знаете, но просто хотите очистить эту точку). Со всеми этими предметными вещами это смешивается. Вы нуждаетесь в них очень часто в ОС для управления блокировками и семафорами, поэтому многие процессоры имеют атомарные целые инструкции. Для float они обычно не реализуются, поэтому они реализуются, обертывая операцию float в блоке, защищенном семафором (который реализуется с помощью атомных ints).

В высокоуровневой java нет никакой проблемы сделать эту блокировку для самих поплавков (и вы правы, они могли бы ее реализовать), но для эффективности вы должны реализовать их с низким уровнем asm, поэтому очень практично, если вы предоставите для людей высокого уровня java некоторые функции, которые используют инструкции abm низкого уровня.

В действительности я видел очень редко приложения, в которых операции атомного флота полезны. Я наткнулся на них, но очень редок, и всегда можно было переформулировать проблему, что concurrency не произошел в поплавковой части.

0

Я нашел библиотеку с 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>
0

Хотя некоторые из ответов здесь некоторые, реализация не предлагает полный и полный.

Этот. Это AtomicDouble, а не AtomicFloat, поскольку он имеет более высокую точность, чем float.

Как некоторые из реализаций, размещенных здесь, включая google guava, им не нужны функции обновления, поэтому такие операции, как:

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

}
0

Это не проблема Java, все языки страдают от этого.

Инструкции по сборке, с которыми скомпилированы атомные сравнения и операции свопинга, являются вариантами: http://x86.renejeschke.de/html/file_module_x86_id_41.html

Все они работают с целыми числами, а конвейерный характер FPU значительно усложняет реализацию для float/double.

0

Вы уверены, что вам это нужно?

Атомные классы разрабатываются в основном как строительные блоки для реализации неблокирующих структур данных и связанных с ними классов инфраструктуры. Метод compareAndSet не является общей заменой для блокировки. Он применяется только тогда, когда критические обновления для объекта ограничиваются одной переменной.

Здесь объясняется проблема, которую атомические переменные были разработаны для решения.

  • 4
    Вы уверены, что вам это нужно? - Возможно, ему просто любопытно :-) Я думаю, что это совершенно законный вопрос.
  • 2
    @aioobe Да, но я просто думаю, что лучше прочитать о том, почему существует AtomicInteger чем предоставить решение, которое, вероятно, действительно не нужно.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню