Что делает AtomicBoolean, чего не может достичь изменчивое логическое значение?
Что делает AtomicBoolean, чего не может достичь изменчивое логическое значение?
Ответы:
Они просто совершенно разные. Рассмотрим пример целого 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
служит подсказкой компилятору, чтобы он был немного осторожнее с оптимизацией.
volatile
. Вопрос о volatile boolean
против AtomicBoolean
.
volatile boolean
достаточно. Если есть также много авторов, вам может понадобиться AtomicBoolean
.
Я использую изменчивые поля, когда упомянутое поле ТОЛЬКО ОБНОВЛЯЕТСЯ потоком его владельца, а значение читается только другими потоками, вы можете рассматривать его как сценарий публикации / подписки, в котором есть много наблюдателей, но только один издатель. Однако, если эти наблюдатели должны выполнить некоторую логику, основанную на значении поля, а затем отодвинуть новое значение, тогда я использую Atomic * переменные или блокировки или синхронизированные блоки, что мне больше подходит. Во многих параллельных сценариях он сводится к получению значения, сравнению его с другим и обновлению, если необходимо, следовательно, методы CompareAndSet и getAndSet присутствуют в классах Atomic *.
Проверьте JavaDocs пакета java.util.concurrent.atomic для получения списка классов Atomic и отличного объяснения того, как они работают (только что узнали, что они свободны от блокировок, поэтому они имеют преимущество перед блокировками или синхронизированными блоками)
boolean
var, мы должны выбрать volatile boolean
.
Вы не можете сделать это compareAndSet
, getAndSet
как атомарную операцию с volatile boolean (если, конечно, вы не синхронизируете ее).
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
).
volatile
Ключевое слово гарантирует связь между потоками, разделяющими эту переменную. Это не гарантирует, что 2 или более потоков не будут прерывать друг друга при доступе к этой логической переменной.
Atomic*
Класс обертывание volatile
поля.
Летучий логический против AtomicBoolean
Классы Atomic * обертывают летучий примитив того же типа. Из источника:
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 *, то вместо этого у вас может быть просто изменчивое поле.
Что делает AtomicBoolean, чего не может достичь изменчивое логическое значение?
Атомные * классы предоставляют методы , которые обеспечивают более расширенные функциональные возможности, такие как incrementAndGet()
, compareAndSet()
и другие , которые реализуют несколько операций (GET / инкремент / комплект, тест / набора) без блокировки. Вот почему классы Atomic * такие мощные.
Например, если несколько потоков используют следующий код с использованием ++
, будут условия состязания, потому что ++
на самом деле: get, increment и set.
private volatile value;
...
// race conditions here
value++;
Однако следующий код будет безопасно работать в многопоточной среде без блокировок:
private final AtomicLong value = new AtomicLong();
...
value.incrementAndGet();
Также важно отметить, что перенос вашего изменчивого поля с использованием класса Atomic * является хорошим способом инкапсуляции критического общего ресурса с точки зрения объекта. Это означает, что разработчики не могут просто иметь дело с полем, предполагая, что оно не является общим, что может привести к проблемам с полем ++; или другой код, который вводит условия гонки.
Если есть несколько потоков, обращающихся к переменной класса, тогда каждый поток может сохранить копию этой переменной в своем локальном кеше.
Если сделать переменную volatile, потоки не смогут сохранить копию переменной в локальном кеше.
Атомные переменные различны, и они допускают атомное изменение их значений.
Примитивный тип Boolean является атомарным для операций записи и чтения, а volatile гарантирует принцип «происходит раньше». Так что если вам нужны простые get () и set (), вам не нужен AtomicBoolean.
С другой стороны, если вам нужно реализовать некоторую проверку перед установкой значения переменной, например, «если истина, а затем установить на ложь», то вам нужно выполнить эту операцию также атомарно, в этом случае используйте compareAndSet и другие методы, предоставляемые AtomicBoolean, поскольку, если вы попытаетесь реализовать эту логику с помощью volatile boolean, вам потребуется некоторая синхронизация, чтобы убедиться, что значение не изменилось между get и set.
Помните ИДИОМ -
ЧИТАЙТЕ - ИЗМЕНИТЕ - НАПИШИТЕ это, чего вы не можете достичь с помощью volatile
volatile
работает только в тех случаях, когда поток владельца может обновлять значение поля, а другие потоки могут только читать.
Если у вас есть только один поток, модифицирующий ваш логический тип, вы можете использовать логический тип volatile (обычно вы делаете это для определения stop
переменной, проверяемой в основном цикле потока).
Однако, если у вас есть несколько потоков, модифицирующих логическое значение, вы должны использовать AtomicBoolean
. Иначе, следующий код не является безопасным:
boolean r = !myVolatileBoolean;
Эта операция выполняется в два этапа:
Если другой поток изменяет значение между #1
и 2#
, вы можете получить неверный результат. AtomicBoolean
методы избежать этой проблемы, делая шаги #1
и #2
атомарно.
Оба имеют одну и ту же концепцию, но в атомарном логическом значении это обеспечит атомарность операции в случае переключения процессора между ними.