10 ответов:
есть два основных применения
AtomicInteger:
как атомарный счетчик (
incrementAndGet()и т. д.) , которые могут использоваться многими потоками одновременнокак примитив, который поддерживает compare-and-swap инструкции (
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()) вместо инкремента (и обрабатывает результат перед возвратом).
самый простой пример, который я могу придумать, - это сделать приращение атомной операции.
со стандартными ints:
private volatile int counter; public int getNextUniqueIndex() { return counter++; // Not atomic, multiple threads could get the same result }С AtomicInteger:
private AtomicInteger counter; public int getNextUniqueIndex() { return counter.getAndIncrement(); }последний является очень простым способом выполнения простых эффектов мутаций (особенно подсчета или уникальной индексации), без необходимости прибегать к синхронизации всего доступа.
более сложная логика без синхронизации может быть использована с помощью
compareAndSet()как тип оптимистическая блокировка-получить текущее значение, вычислить результат на основе этого, установить этот результат iff значение по - прежнему используется для вычисления, иначе начните снова-но примеры подсчета очень полезны, и я часто буду использоватьAtomicIntegersдля подсчета и VM-wide уникальных генераторов, если есть какой-либо намек на участие нескольких потоков, потому что с ними так легко работать, я бы почти счел преждевременной оптимизацию для использования plainints.в то время как вы можете почти всегда достигайте тех же гарантий синхронизации с
intsи соответствующегоsynchronizedзаявления, красотаAtomicIntegerзаключается в том, что потокобезопасность встроена в сам фактический объект, а не вам нужно беспокоиться о возможных перемежениях и мониторах каждого метода, который происходит для доступа кintзначение. Гораздо сложнее случайно нарушить threadsafety при вызовеgetAndIncrement()чем при возвратеi++и вспоминая (или нет), чтобы приобрести правильный набор мониторов заранее.
если вы посмотрите на методы AtomicInteger, вы заметите, что они, как правило, соответствуют общим операциям на ints. Например:
static AtomicInteger i; // Later, in a thread int current = i.incrementAndGet();- это потокобезопасная версия:
static int i; // Later, in a thread int current = ++i;методы сопоставляются следующим образом:
++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быстрее и читабельнее, чем выполнять то же самое с помощью синхронизации.простой тест:
public synchronized int incrementNotAtomic() { return notAtomic++; } public void performTestNotAtomic() { final long start = System.currentTimeMillis(); for (int i = 0 ; i < NUM ; i++) { incrementNotAtomic(); } System.out.println("Not atomic: "+(System.currentTimeMillis() - start)); } public void performTestAtomic() { final long start = System.currentTimeMillis(); for (int i = 0 ; i < NUM ; i++) { atomic.getAndIncrement(); } System.out.println("Atomic: "+(System.currentTimeMillis() - start)); }на моем ПК с Java 1.6 атомный тест выполняется за 3 секунды, а синхронизированный - примерно за 5,5 секунды. Проблема здесь в том, что операция синхронизации (
notAtomic++) действительно короткий. Так что стоимость синхронизации действительно важна по сравнению с операцией.кроме атомарности AtomicInteger можно использовать как изменяемую версию
IntegerнапримерMapкак ценности.
например, у меня есть библиотека, которая генерирует экземпляры какого-то класса. Каждый из этих экземпляров должен иметь уникальный целочисленный идентификатор, поскольку эти экземпляры представляют команды, отправляемые на сервер, и каждая команда должна иметь уникальный идентификатор. Поскольку несколько потоков могут отправлять команды одновременно, я использую AtomicInteger для создания этих идентификаторов. Альтернативным подходом было бы использовать какой-то замок и обычное целое число, но это медленнее и менее элегантно.
Как сказал габузо, иногда я использую AtomicIntegers, когда хочу передать int по ссылке. Это встроенный класс, который имеет архитектурный код, поэтому он проще и, вероятно, более оптимизирован, чем любой MutableInteger, который я мог бы быстро закодировать. Тем не менее, это похоже на злоупотребление классом.
в Java 8 атомарных классов были расширены с помощью двух интересных функций:
- int getAndUpdate (IntUnaryOperator updateFunction)
- int updateAndGet (IntUnaryOperator updateFunction)
оба используют функцию updateFunction для выполнения обновления атомарного значения. Разница в том, что первый возвращает старое значение, а второй возвращает новое значение. Функция updateFunction может быть реализована для более сложного " сравнения и установки" операции, чем стандартный. Например, он может проверить, что атомарный счетчик не опускается ниже нуля, обычно это требует синхронизации, и здесь код не блокируется:
public class Counter { private final AtomicInteger number; public Counter(int number) { this.number = new AtomicInteger(number); } /** @return true if still can decrease */ public boolean dec() { // updateAndGet(fn) executed atomically: return number.updateAndGet(n -> (n > 0) ? n - 1 : n) > 0; } }код взят из Атомарный Пример Java.
Я обычно использую AtomicInteger, когда мне нужно дать идентификаторы объектам, которые могут быть доступны или созданы из нескольких потоков, и я обычно использую его в качестве статического атрибута в классе, к которому я обращаюсь в конструкторе объектов.
вы можете реализовать неблокирующие блокировки с помощью compareAndSwap (CAS) на атомарных целых числах или лонгах. Элемент Транзакционная Память Программного Обеспечения "Tl2" документ описывает это:
мы связываем специальную версионную блокировку записи с каждой транзакцией ячейка памяти. В своей простейшей форме версионная блокировка записи представляет собой одним словом блокировка, использующая операция CAS получить блокировку и магазин, чтобы выпустить его. Так как нужно только один бит, чтобы указать что замок взят, мы используем остальную часть слова замка, чтобы держать номер версии.
то, что он описывает, сначала читается атомарное целое число. Разделите это на игнорируемый бит блокировки и номер версии. Попытайтесь записать его как бит блокировки, очищенный с текущим номером версии в набор бит блокировки и следующий номер версии. Цикл, пока вы не добьетесь успеха, и ваш поток, который владеет замком. Разблокируйте, установив текущий номер версии с отключенным блокирующим битом. Этот в статье описывается использование номеров версий в блокировках для координации того, что потоки имеют согласованный набор операций чтения при записи.
в этой статье описывает, что процессоры имеют аппаратную поддержку для сравнения и операций подкачки, что делает их очень эффективными. Он также утверждает:
неблокирующие счетчики на основе CAS с использованием атомарных переменных лучше производительность, чем счетчики на основе блокировки в низкой и умеренной конкуренции
ключ в том, что они позволяют одновременный доступ и модификацию безопасно. Они обычно используются в качестве счетчиков в многопоточной среде - до их введения это должен был быть написанный пользователем класс, который заключал различные методы в синхронизированные блоки.
Comments