Что делает AtomicBoolean, что невозможно достичь летучего логического значения?
Volatile boolean vs AtomicBoolean
Ответ 1
Они просто совершенно разные. Рассмотрим этот пример целого числа volatile
:
volatile int i = 0;
void incIBy5() {
i += 5;
}
Если два потока одновременно вызывают функцию, i
может быть 5 после этого, так как скомпилированный код будет несколько похож на это (за исключением того, что вы не можете синхронизировать на int
):
void incIBy5() {
int temp;
synchronized(i) { temp = i }
synchronized(i) { i = temp + 5 }
}
Если переменная изменчива, каждый атомный доступ к ней синхронизируется, но не всегда очевидно, что на самом деле квалифицируется как атомный доступ. С объектом Atomic*
гарантируется, что каждый метод является "атомарным".
Таким образом, если вы используете AtomicInteger
и getAndAdd(int delta)
, вы можете быть уверены, что результат будет 10
. Точно так же, если два потока одновременно сжимают переменную boolean
одновременно, с AtomicBoolean
вы можете быть уверены, что она имеет исходное значение после этого, с volatile boolean
вы не можете.
Поэтому всякий раз, когда у вас есть несколько потоков, изменяющих поле, вам нужно сделать его атомарным или использовать явную синхронизацию.
Цель volatile
- другая. Рассмотрим этот пример
volatile boolean stop = false;
void loop() {
while (!stop) { ... }
}
void stop() { stop = true; }
Если у вас есть поток с именем loop()
и другой поток, вызывающий stop()
, вы можете запустить бесконечный цикл, если вы опустите volatile
, так как первый поток может кэшировать значение stop. Здесь volatile
служит подсказкой для компилятора, чтобы быть более осторожным с оптимизациями.
Ответ 2
Я использую изменчивые поля, когда указанное поле ТОЛЬКО ОБНОВЛЯЕТСЯ своим потоком владельца, и значение считывается только другими потоками, вы можете думать об этом как о публикации/подписке, где есть много наблюдателей, но только один издатель. Однако, если эти наблюдатели должны выполнить некоторую логику, основанную на значении поля, а затем отбросить новое значение, тогда я иду с Atomic * vars или locks или синхронизированными блоками, что мне больше всего подходит. Во многих параллельных сценариях сводится к тому, чтобы получить значение, сравнить его с другим и при необходимости обновить, следовательно, методы compareAndSet и getAndSet, присутствующие в классах Atomic *.
Проверьте JavaDocs пакета java.util.concurrent.atomic для списка классов Atomic и отличного объяснения того, как они работают ( просто узнали, что они заблокированы, поэтому они имеют преимущество перед блокировками или синхронизированными блоками)
Ответ 3
Вы не можете сделать compareAndSet
, getAndSet
как атомную операцию с volatile boolean (если, конечно, вы ее не синхронизируете).
Ответ 4
AtomicBoolean
имеет методы, которые выполняют свои сложные операции атомарно и без использования блока synchronized
. С другой стороны, volatile boolean
может выполнять только сложные операции, если это делается в блоке synchronized
.
Эффекты памяти при чтении/записи на volatile boolean
идентичны методам get
и set
AtomicBoolean
соответственно.
Например, метод compareAndSet
будет атомарно выполнять следующее (без блока synchronized
):
if (value == expectedValue) {
value = newValue;
return true;
} else {
return false;
}
Следовательно, метод compareAndSet
позволит вам писать код, который гарантированно будет выполняться только один раз, даже если он вызван из нескольких потоков. Например:
final AtomicBoolean isJobDone = new AtomicBoolean(false);
...
if (isJobDone.compareAndSet(false, true)) {
listener.notifyJobDone();
}
Гарантируется, что только один раз уведомит слушателя (если ни один другой поток не вернет AtomicBoolean
обратно в false
снова после того, как он установлен на true
).
Ответ 5
volatile
гарантируется ключевое слово - до отношения между потоками, использующими эту переменную. Это не гарантирует, что два или более потока не будут прерывать друг друга при доступе к этой логической переменной.
Ответ 6
Если существует несколько потоков, обращающихся к переменной уровня класса, тогда каждый поток может сохранить копию этой переменной в своем кэше threadlocal.
Внесение переменной volatile предотвратит сохранение потоками данных в переменной threadoocal cache.
Атомные переменные различны и позволяют атомную модификацию своих значений.
Ответ 7
Помните IDIOM -
READ - MODIFY-WRITE, которого вы не можете достичь с изменчивым
Ответ 8
Логический примитивный тип является атомарным для операций записи и чтения, волатильность гарантирует принцип "бычий". Поэтому, если вам нужны простые get() и set(), вам не нужен AtomicBoolean.
С другой стороны, если вам нужно выполнить некоторую проверку перед установкой значения переменной, например. "если true, то установите значение false", вам также нужно выполнить эту операцию атомарно, в этом случае используйте compareAndSet и другие методы, предоставляемые AtomicBoolean, так как если вы попытаетесь реализовать эту логику с volatile boolean, вам понадобится некоторая синхронизация с убедитесь, что значение не изменилось между get и set.
Ответ 9
Атомные * классы переносят летучий примитив того же типа. Из источника:
public class AtomicLong extends Number implements java.io.Serializable {
...
private volatile long value;
...
public final long get() {
return value;
}
...
public final void set(long newValue) {
value = newValue;
}
Итак, если все, что вы делаете, это получение и установка Atomic *, тогда вы можете просто вместо этого использовать поле volatile.
Что делает AtomicBoolean, что невозможно достичь летучего логического значения?
Однако, что дают вам классы Atomic *, это методы, которые предоставляют более расширенные функции, такие как incrementAndGet()
, compareAndSet()
и другие, которые реализуют несколько операций (get/increment/set, test/set) без блокировки. Вот почему классы Atomic * настолько мощные.
Например, следующий код будет работать в многопоточной среде безопасно:
private final AtomicLong value = new AtomicLong();
...
value.incrementAndGet();
Однако, если несколько потоков используют следующее, будут условия гонки, потому что ++
на самом деле: get, increment и set.
private volatile value;
...
// race conditions here
value++;
Также важно отметить, что упаковка вашего изменчивого поля с использованием класса Atomic * - хороший способ инкапсулировать критически важный общий ресурс с точки зрения объекта. Это означает, что разработчики не могут просто иметь дело с полем, предполагая, что он не разделен, возможно, при введении проблем с полем ++; или другой код, который вводит условия гонки.
Ответ 10
Если у вас есть только один поток, изменяющий ваше логическое значение, вы можете использовать volatile boolean (обычно вы делаете это, чтобы определить переменную stop
, отмеченную в основном цикле потока).
Однако, если у вас есть несколько потоков, изменяющих логическое значение, вы должны использовать AtomicBoolean
. Кроме того, следующий код небезопасен:
boolean r = !myVolatileBoolean;
Эта операция выполняется в два этапа:
- Булево значение читается.
- Булево значение записывается.
Если другой поток изменит значение между #1
и 2#
, вы можете получить неправильный результат. AtomicBoolean
методы избегают этой проблемы, делая шаги #1
и #2
атомарно.