Летучий логический против 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. Логическое значение записывается.

Если другой поток изменяет значение между #1 а также 2#Вы можете получить неверный результат. AtomicBoolean методы избежать этой проблемы, делая шаги #1 а также #2 атомарно.

Примитивный тип Boolean является атомарным для операций записи и чтения, а volatile гарантирует принцип "происходит раньше". Так что если вам нужны простые get() и set(), тогда вам не нужен AtomicBoolean.

С другой стороны, если вам нужно реализовать некоторую проверку перед установкой значения переменной, например, "если истина, а затем установить на ложь", то вам нужно выполнить эту операцию также атомарно, в этом случае используйте compareAndSet и другие методы, предоставляемые AtomicBoolean, поскольку, если вы попытаетесь реализовать эту логику с помощью volatile boolean, вам потребуется некоторая синхронизация, чтобы быть уверенным, что значение не изменилось между get и set.

Оба имеют одну и ту же концепцию, но в атомарном логическом значении это обеспечит атомарность операции в случае, если переключение процессора произойдет между ними.

Другие вопросы по тегам