Есть два основных использования AtomicInteger
:
В качестве атомарного счетчика ( incrementAndGet()
и т. Д.), Который может использоваться многими потоками одновременно
В качестве примитива, который поддерживает инструкцию сравнения и обмена (compareAndSet()
) для реализации неблокирующих алгоритмов.
Вот пример неблокирующего генератора случайных чисел из Java-параллелизма Брайана Гетца на практике :
public class AtomicPseudoRandom extends PseudoRandom {
private AtomicInteger seed;
AtomicPseudoRandom(int seed) {
this.seed = new AtomicInteger(seed);
}
public int nextInt(int n) {
while (true) {
int s = seed.get();
int nextSeed = calculateNext(s);
if (seed.compareAndSet(s, nextSeed)) {
int remainder = s % n;
return remainder > 0 ? remainder : remainder + n;
}
}
}
...
}
Как вы можете видеть, он в основном работает почти так же, как и incrementAndGet()
, но выполняет произвольное вычисление ( calculateNext()
) вместо приращения (и обрабатывает результат перед возвратом).
read
иwrite that value + 1
операции, это обнаруживается , а не перезаписывать старые обновления (избегая «потерянное обновление» проблему). На самом деле это особый случайcompareAndSet
- если старое значение было2
, класс фактически вызываетcompareAndSet(2, 3)
- поэтому, если другой поток изменил значение за это время, метод приращения эффективно перезапускается с самого начала.Абсолютно простейший пример, который я могу придумать, - сделать инкрементную атомарную операцию
Со стандартными целями:
С AtomicInteger:
Последнее является очень простым способом выполнения простых эффектов мутаций (особенно подсчета или уникальной индексации), без необходимости прибегать к синхронизации всего доступа.
Более сложную логику без синхронизации можно использовать, используя
compareAndSet()
в качестве типа оптимистической блокировки - получить текущее значение, вычислить результат на его основе, установить этот результат, если значение по-прежнему остается входным значением, используемым для вычисления, в противном случае начать снова - но примеры подсчета очень полезны, и я часто буду использоватьAtomicIntegers
для подсчета и генераторов уникальных для всей VM, если есть какой-то намек на участие нескольких потоков, потому что с ними так легко работать, что я почти считаю преждевременной оптимизацию, чтобы использовать простойints
,Хотя вы почти всегда можете добиться одинаковых гарантий синхронизации с
ints
соответствующимиsynchronized
декларациями, прелесть вAtomicInteger
том, что безопасность потока встроена в сам фактический объект, а не в беспокойство по поводу возможных чередований и удерживаемых мониторов каждого метода. это происходит для доступа кint
значению. Случайно нарушить безопасность потоков при вызове гораздо сложнее,getAndIncrement()
чем при возвратеi++
и запоминании (или нет), чтобы заранее получить правильный набор мониторов.источник
Если вы посмотрите на методы, которые есть у AtomicInteger, вы заметите, что они, как правило, соответствуют обычным операциям над целыми числами. Например:
является потокобезопасной версией этого:
Методы отображения так:
++i
какi.incrementAndGet()
i++
этоi.getAndIncrement()
--i
естьi.decrementAndGet()
i--
вi.getAndDecrement()
i = x
этоi.set(x)
x = i
являетсяx = i.get()
Есть и другие удобные методы, такие как
compareAndSet
илиaddAndGet
источник
Основное использование
AtomicInteger
- это когда вы находитесь в многопоточном контексте, и вам нужно выполнять потокобезопасные операции над целым числом без использованияsynchronized
. Присвоение и извлечение примитивного типаint
уже атомарны, ноAtomicInteger
идут со многими операциями, которые не являются атомарнымиint
.Простейшими являются
getAndXXX
илиxXXAndGet
. НапримерgetAndIncrement()
, это атомарный эквивалент,i++
который не является атомарным, потому что это на самом деле сокращение для трех операций: поиск, добавление и присваивание.compareAndSet
очень полезно для реализации семафоров, замков, защелок и т. д.Использование
AtomicInteger
быстрее и удобочитаемее, чем выполнение синхронизации с использованием синхронизации.Простой тест:
На моем ПК с Java 1.6 атомарный тест выполняется за 3 секунды, а синхронизированный - около 5,5 секунд. Проблема в том, что операция synchronize (
notAtomic++
) действительно короткая. Поэтому стоимость синхронизации действительно важна по сравнению с операцией.Помимо атомарности AtomicInteger может использоваться как изменяемая версия,
Integer
например, вMap
качестве значений.источник
AtomicInteger
в качестве ключа карты, потому что он используетequals()
реализацию по умолчанию , которая почти наверняка не соответствует ожидаемой семантике при использовании в карте.Например, у меня есть библиотека, которая генерирует экземпляры некоторого класса. Каждый из этих экземпляров должен иметь уникальный целочисленный идентификатор, поскольку эти экземпляры представляют команды, отправляемые на сервер, и каждая команда должна иметь уникальный идентификатор. Поскольку нескольким потокам разрешено отправлять команды одновременно, я использую AtomicInteger для генерации этих идентификаторов. Альтернативный подход заключается в использовании некоторого типа блокировки и обычного целого числа, но это и медленнее, и менее элегантно.
источник
Как сказал Габузо, иногда я использую AtomicIntegers, когда хочу передать int по ссылке. Это встроенный класс, в котором есть специфичный для архитектуры код, поэтому он проще и, вероятно, более оптимизирован, чем любой MutableInteger, который я мог бы быстро кодировать. Тем не менее, это похоже на злоупотребление классом.
источник
В Java 8 атомарные классы были расширены двумя интересными функциями:
Оба используют функцию updateFunction для обновления атомарного значения. Разница в том, что первое возвращает старое значение, а второе возвращает новое значение. Функция updateFunction может быть реализована для выполнения более сложных операций «сравнивать и устанавливать», чем стандартная. Например, он может проверить, что атомный счетчик не опускается ниже нуля, обычно это требует синхронизации, и здесь код не блокируется:
Код взят из Java Atomic Example .
источник
Я обычно использую AtomicInteger, когда мне нужно дать идентификаторы объектам, которые могут быть приняты или созданы из нескольких потоков, и я обычно использую его как статический атрибут в классе, к которому я обращаюсь в конструкторе объектов.
источник
Вы можете реализовать неблокирующие блокировки, используя compareAndSwap (CAS) для атомарных целых или длинных значений. В документе «Транзакционная память программного обеспечения Tl2» описывается это:
То, что он описывает, это сначала прочитать атомное целое число. Разделите это на игнорируемый бит блокировки и номер версии. Попытайтесь в CAS записать его как бит блокировки, сброшенный с номером текущей версии, в установленный бит блокировки и следующий номер версии. Цикл, пока вы не добьетесь успеха и ваша нить владеет замком. Разблокируйте, установив номер текущей версии с очищенным битом блокировки. В статье описывается использование номеров версий в замках для координации того, что потоки имеют постоянный набор операций чтения при записи.
В этой статье описывается, что процессоры имеют аппаратную поддержку для операций сравнения и обмена, что делает их очень эффективными. Он также утверждает:
источник
Ключ в том, что они позволяют одновременный доступ и изменение безопасно. Они обычно используются в качестве счетчиков в многопоточной среде - до их появления это должен был быть класс, написанный пользователем, который упаковывал различные методы в синхронизированные блоки.
источник
Я использовал AtomicInteger для решения проблемы Обедающего Философа.
В моем решении экземпляры AtomicInteger использовались для представления вилок, для каждого философа необходимы два. Каждый Философ обозначается как целое число от 1 до 5. Когда философ использует вилку, AtomicInteger содержит значение философа, от 1 до 5, в противном случае вилка не используется, поэтому значение AtomicInteger равно -1. ,
Затем AtomicInteger позволяет проверить, свободен ли разветвление, значение == - 1, и установить его владельцем разветвления, если он свободен, за одну атомарную операцию. Смотрите код ниже.
Поскольку метод compareAndSet не блокирует, он должен увеличить пропускную способность, сделать больше работы. Как вы, возможно, знаете, проблема «Обедающие философы» используется, когда требуется контролируемый доступ к ресурсам, то есть нужны вилки, как процесс требует ресурсов для продолжения работы.
источник
Простой пример для функции compareAndSet ():
Напечатано: предыдущее значение: 0 Значение обновлено, и оно равно 6. Другой простой пример:
Напечатано: Предыдущее значение: 0 Значение не было обновлено
источник