Как LongAdder работает лучше, чем AtomicLong

Я вижу, как Java AtomicInteger работает внутри с CAS (Compare and Swap). В основном, когда несколько потоков пытаются обновить значение, JVM внутренне использует базовый механизм CAS и пытается обновить значение. Если обновление не удалось, повторите попытку с новым значением, но никогда не блокируйте.

В Java8 Oracle представил новый класс LongAdder, который, по-видимому, работает лучше, чем AtomicInteger. Некоторые сообщения в блоге утверждают, что LongAdder лучше работает, поддерживая внутренние ячейки. Значит ли это, что LongAdder агрегирует значения внутри и обновляет их позже? Не могли бы вы помочь мне понять, как работает LongAdder?

Ответ 1

означает ли это, что LongAdder агрегирует значения внутри и обновляет их позже?

Да, если я правильно понимаю ваше утверждение.

Каждый Cell в LongAdder является вариантом AtomicLong. Наличие нескольких таких ячеек является способом распространения конкуренции и, следовательно, увеличения пропускной способности.

Когда конечный результат (сумма) должен быть восстановлен, он просто добавляет вместе значения каждой ячейки.

Значительная часть логики вокруг того, как организованы ячейки, как они выделены и т.д., можно увидеть в источнике: http://hg.openjdk.java.net/jdk9/jdk9/jdk/file/f398670f3da7/src/java.base/share/classes/java/util/concurrent/atomic/Striped64.java

В частности, количество ячеек связано числом процессоров:

/** Number of CPUS, to place bound on table size */
static final int NCPU = Runtime.getRuntime().availableProcessors();

Ответ 2

Основная причина, по которой он "быстрее", - это заявленная производительность. Это важно, потому что:

В условиях низкого соответствия обновления два класса имеют схожие характеристики.

Вы используете LongAdder для очень частых обновлений, в которых атомарные CAS и собственные вызовы на Unsafe могут вызвать конфликт. (Смотрите источник и volatile читает). Не говоря уже о промахе кеша/ложном общении на нескольких AtomicLongs (хотя я еще не смотрел макет класса, похоже, не хватает заполнение памяти перед фактическим полем long.

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

Реализация расширяет Striped64, которая является держателем данных для 64-битных значений. Значения сохраняются в ячейках, которые дополняются (или чередуются), отсюда и название. Каждая операция может на LongAdder изменять набор значений, присутствующих в Striped64. Когда возникает конфликт, создается и изменяется новая ячейка, поэтому старый поток может заканчиваться одновременно с конкурирующим. Когда вам нужно окончательное значение, суммы каждой ячейки просто складываются.

К сожалению, производительность идет со стоимостью, которая в этом случае является памятью (как это часто бывает). Striped64 может стать очень большим, если на него бросается большая загрузка потоков и обновлений.

Источник цитаты: Javadoc для LongAdder