Модульное приращение с классами Java Atomic

Я был удивлен, что классы Java AtomicInteger и AtomicLong не имеют методов для модульных приращений (так что значение обнуляется после достижения предела).

Я полагаю, что должен упустить что-то очевидное. Как лучше всего это сделать?

Например, я хочу разделить простой int между потоками, и я хочу, чтобы каждый поток мог увеличивать его, скажем, на мод 10.

Я могу создать класс, использующий синхронизацию/блокировку, но есть ли лучший и более простой способ?


person Community    schedule 26.01.2011    source источник


Ответы (5)


Что сложного в добавлении модификатора synchronized или блока к вашему методу addModular()?

Причина, по которой классы Atomic не имеют этой функциональности, заключается в том, что они основаны на специфических атомарных аппаратные инструкции, предлагаемые современными ЦП, и модульная арифметика не могут быть реализованы без использования блокировки или других более сложных и потенциально неэффективных алгоритмов, таких как предложенный Мэттом.

person Michael Borgwardt    schedule 26.01.2011

Просто мод 10 значение, когда вы читаете из него?

public class AtomicWrappingCounter {
  private final AtomicLong counter = new AtomicLong();
  private final int max;

  public AtomicWrappingCounter(int max) {
    this.max = max;
  }

  public int get() {
    return (int) (counter.get() % max);
  }

  public int incrementAndGet() {
    return (int) (counter.incrementAndGet() % max);
  }
}

Очевидно, что если вы можете увеличить этот счетчик более чем в 2_ раза, вы не сможете использовать этот подход, но 9 квинтиллионов — это много раз для увеличения (около 292 лет со скоростью 1 в наносекунду!).

person ColinD    schedule 26.01.2011
comment
Разве эти методы не нужно синхронизировать, ColinD? Что, если один поток находится внутри incrementAndGet(), и приращение завершено, но не по модулю, а другой поток вызывает get() и возвращает увеличенное, но не по модулю значение? - person ; 20.10.2016
comment
@Mark: Нет. Модуль является локальным для каждого потока. AtomicLong гарантирует, что после вызова incrementAndGet() другой поток, вызывающий get(), увидит новое значение. Оба потока затем вычисляют значение по модулю самостоятельно, и каждый видит ожидаемый окончательный результат. - person ColinD; 24.10.2016

В Java 8 вы можете использовать getAndUpdateupdateAndGet) в AtomicInteger.

Например, если мы хотим иметь счетчик, который обнуляется каждый раз, когда он достигает 10.

AtomicInteger counter = new AtomicInteger(0);

// to get & update
counter.getAndUpdate(value -> (value + 1) % 10)
person cakraww    schedule 27.06.2018

Я думаю, что самый простой способ - создать счетчик обертывания самостоятельно, который хранит свои значения в AtomicInteger, что-то вроде

public class AtomicWrappingCounter {
    private AtomicInteger value;
    private final int max;

    public AtomicWrappingCounter(int start, int max) {
        this.value = new AtomicInteger(start);
        this.max = max;
    }

    public int get() {
        return value.get();
    }

    /* Simple modification of AtomicInteger.incrementAndGet() */
    public int incrementAndGet() {
        for (;;) {
            int current = get();
            int next = (current + 1) % max;
            if (value.compareAndSet(current, next))
                return next;
        }
    }
}

Почему AtomicInteger сам не предоставляет что-то подобное? Кто знает, но я думаю, что намерение авторов среды параллелизма состояло в том, чтобы предоставить некоторые строительные блоки, которые вы могли бы использовать для лучшего создания своих собственных функций более высокого уровня.

person matt b    schedule 26.01.2011
comment
Им нужно только реализовать get и compareAndSet в фреймворке. Все остальные методы могут быть построены на них. - person finnw; 27.01.2011
comment
Умно, но я подозреваю, что в условиях сильной конкуренции это на самом деле будет работать хуже, чем синхронизация. - person Michael Borgwardt; 27.01.2011
comment
@Майкл, можешь уточнить, почему? Будет ли это отличаться от собственного поведения AtomicInteger? - person matt b; 27.01.2011
comment
@matt: чем больше потоков одновременно выполняет ваш код, тем выше вероятность того, что compareAndSet() не удастся, и вам придется повторить попытку. Если конкуренция очень высока, все потоки будут тратить большую часть своего времени на повторное выполнение цикла. Это отличается от самого compareAndSet(), который (на поддерживающем его оборудовании) реализуется фактически атомарными инструкциями ЦП. - person Michael Borgwardt; 27.01.2011
comment
@Michael: Дело в том, что incrementAndGet() реализован так же, как его метод здесь, за исключением того, что без мода. - person ColinD; 27.01.2011
comment
@ColinD: ... и он работает хуже, чем синхронизация в условиях сильной конкуренции: stackoverflow.com/questions/3556283/ - person Michael Borgwardt; 27.01.2011
comment
Что ж, Майкл прав. В условиях жесткой конкуренции реализации без блокировки с небесконечным консенсусом будут работать хуже, чем альтернатива с блокировкой. И, чтобы ответить на мат б, тот же тип проблемы относится ко всем операциям без сравнения и установки, например, getAndAdd. - person John Vint; 27.01.2011
comment
Для дальнейшего доказательства. Вы можете посмотреть на LinkedBlockingQueue и ConcurrentLinkedQueue и увидеть, что при обычном состязании CLQ намного превосходит LBQ, но при сильном состязании LBQ лучше. - person John Vint; 27.01.2011
comment
Правильно, поэтому в такой ситуации вы все равно не захотите использовать классы Atomic, ситуация, которая в основном сводит на нет суть этого вопроса. Хорошая информация, чтобы знать, хотя. - person matt b; 27.01.2011
comment
Я не хочу думать о том, что произойдет, если start будет вне [0, max) или если max будет неположительным. value должно быть final. / Также compareAndSet следует вызывать на value, и очень странно иметь cas-loop с таким возвратом вместо do-while. - person Tom Hawtin - tackline; 17.03.2011
comment
@Tom Hawtin Я не уверен, что понимаю, что вы имеете в виду - как упоминалось выше, incrementAndGet() в моем образце точно такой же, как реализация AtomicInteger в Sun JDK с небольшой модификацией. Цикл cas с возвратом напрямую взят из кода Sun. Что касается проверки параметров, переданных конструктору, это всего лишь пример кода, а правильная проверка/утверждение инвариантов — это упражнение, которое лучше оставить читателю. - person matt b; 17.03.2011
comment
В приведенном выше коде max никогда не будет достигнуто. Вместо этого мы должны модифицировать с max + 1. - person Akshay Gehi; 12.11.2016

Я был удивлен, что классы Java AtomicInteger и AtomicLong не имеют методов для модульных приращений.

Не удивляйтесь, если стандартный класс не включает «прибамбасы» для поддержки всевозможных необычных вариантов использования. Дизайнеры должны провести черту между тем, что они включают, а что нет. Тенденция состоит в том, чтобы поддерживать общие варианты использования и варианты использования, которые невозможно поддерживать каким-либо другим способом. В данном случае ни один из этих критериев не применим.

person Stephen C    schedule 26.01.2011