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