Volatile boolean vs AtomicBoolean

Что делает 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 атомарно.