Почему AtomicBoolean не может быть заменой Boolean?

Oracle JDK Javadoc для AtomicBoolean заявляет:

https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/atomic/AtomicBoolean.html

Логическое значение, которое может быть обновлено атомарно. См. Спецификацию пакета java.util.concurrent.atomic для описания свойств атомарных переменных. AtomicBoolean используется в таких приложениях, как атомарно обновленные флаги, и не может использоваться в качестве замены логического.

Мы с коллегой пытались выяснить вариант использования, в котором AtomicBoolean не может быть заменой, и единственное, о чем мы можем думать, - это то, что существуют методы, которые есть у логического объекта, которых нет у AtomicBoolean.

Это единственная причина или было что-то еще, когда это было написано?

7 ответов

Решение

Boolean это класс-оболочка вокруг примитива boolean, Может быть автоматически создан из boolean компилятором (преобразование бокса) или преобразованным в логическое значение (преобразование без коробки). Это не так для AtomicBoolean где это отдельный класс, предназначенный для целей параллелизма.

Следовательно, два класса имеют разную семантику на уровне языка:

Boolean b = new Boolean(true);
AtomicBoolean ab = new AtomicBoolean(true);
System.out.println(true == b);  // automatic unboxing of Boolean variable
System.out.println(true == ab);  // compiler error

Boolean является неизменным значением объекта. Это было разработано, чтобы быть неизменным и сделанным заключительным, чтобы усилить это. java.lang.Boolean существует с 1.0.

AtomicBoolean является изменяемым и предназначен для обновления таким образом, чтобы обновленное значение было видно в потоках. AtomicBoolean был представлен с Java 5.

Это совершенно разные концепции, поэтому AtomicBoolean не был разработан для расширения Boolean. Вы не можете заменить изменяемый объект неизменным, не разрушая ожидаемые инварианты кода, использующего его. Код, ожидающий получения неизменного значения, может быть поврежден, если вместо него будет передана атомарная версия.

Итак, вот пример использования: если AtomicBoolean был введен как нечто, заменяющее Boolean, вы можете иметь случай, когда класс, созданный до этого изменения, может разумно ожидать, что в каком-то методе, который возвращает Boolean, ему не нужно проходить защитный копия из-за логического неизменяемости. Если возвращаемая ссылка оказывается инициализированной из источника, который переходит на использование AtomicBoolean вместо Boolean, то это поле теперь можно изменить, вызвав метод, возвращающий Boolean, путем приведения его к AtomicBoolean.

Атомарные классы предназначены для работы с параллельными обновлениями (как улучшение volatile), но наиболее эффективный способ разработки параллельного кода - использовать неизменяемые значения. Поэтому будьте осторожны, чтобы не принять AtomicBoolean за "логическое значение, которое вы используете при написании многопоточного кода".

Один из вариантов использования заключается в том, что AtomicBoolean не распространяется Boolean, Таким образом, если у вас есть такой метод, как:

void foo (Boolean b) {
    doStuff();
}

Тогда вы не можете пройти AtomicBoolean в качестве параметра для foo, (Вы должны были бы позвонить get() метод AtomicBoolean.)

Они не могут быть автоматически упакованы, поэтому их нельзя использовать в условных выражениях, например,

// Explodey
if (someAtomicBoolean) {
}

Пример:

void doSomething( final Boolean flag ) {


  final boolean before = flag.booleanValue();

  do0( flag );

  final boolean after = flag.booleanValue();

  assert before == after;



  if ( flag.booleanValue() ) {
    do1();
  }

  if ( flag.booleanValue() ) {
    do2();
  }

}

может дать другой результат, чем

void doSomething( final AtomicBoolean flag ) {


  final boolean before = flag.get();

  do0( flag );

  final boolean after = flag.get();

  assert (before == after) || (before != after);



  if ( flag.get() ) {
    do1();
  }

  if ( flag.get() ) {
    do2();
  }

}

потому что AtomicBoolean может изменить свое значение в то время как Boolean не могу.

В первом случае do1() а также do2() либо оба названы, либо ни один из них.

Во втором случае оба, или ни один из них не могут быть вызваны, если AtomicBooleanЗначение изменяется одновременно.

Так как Boolean всегда был там, и всегда определялся как неизменный, AtomicBoolean, который был введен гораздо позже, не может быть заменен Boolean потому что он ведет себя по-разному и код, который по праву полагается на неизменность Boolean может сломаться, если эта неизменность разрушена.

Заметить, что Boolean не может быть заменено AtomicBoolean и наоборот. Они просто не совместимы в своей семантике.

Так как Boolean неизменен. Смотрите: Почему класс Wrapper, такой как Boolean в Java, является неизменным? и мой ответ:


Так как 2 2. Это не будет 3 завтра.

Immutable всегда предпочитается по умолчанию, особенно в многопоточных ситуациях, и это облегчает чтение и поддержку кода. Показательный пример: Java Date API, который пронизан недостатками дизайна. Если Date были бы неизменными API было бы очень упорядочено. я бы знал Date Операции создадут новые даты и никогда не будут искать API, которые их изменяют.

Прочитайте параллелизм на практике, чтобы понять истинную важность неизменяемых типов.

Но также учтите, что если по какой-то причине вам нужны изменяемые типы, используйте AtomicIntegerAtomicBooleanи т. д. Почему Atomic? Потому что, вводя изменчивость, вы вводили необходимость в безопасности потоков. В чем бы вы не нуждались, если бы ваши типы оставались неизменными, поэтому при использовании изменяемых типов вы также должны заплатить за размышления о безопасности потоков и использование типов из concurrent пакет. Добро пожаловать в удивительный мир параллельного программирования.

Также для Boolean - Я призываю вас назвать одну операцию, которую вы, возможно, захотите выполнить, которая заботится о том, является ли Boolean изменяемым. установить в истину? использование myBool = true, Это переназначение, а не мутация. Отрицание? myBool = !myBool, То же правило. Обратите внимание, что неизменность - это функция, а не ограничение, поэтому, если вы можете предложить ее, вы должны - и в этих случаях, конечно, можете.

Обратите внимание, что это относится и к другим типам. Самая тонкая вещь с целыми числами count++но это просто count = count + 1, если вы не заботитесь о получении значения атомарно... в этом случае используйте изменяемый AtomicInteger,

Первый основной вариант использования атомарных операций (иногда инкапсулированных) в любом языке - это семантика сравнения и обмена - фундаментальные строительные блоки параллельных приложений.

Второе основное использование - скрыть сложность правильного размещения ограждений памяти, необходимых для семантики модели памяти.

В Java Atomic * инкапсулирует оба вышеперечисленных, первый - с помощью собственного кода платформы, а второй - с помощью volatile ключевое слово.

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