Почему 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, которые их изменяют.
Прочитайте параллелизм на практике, чтобы понять истинную важность неизменяемых типов.
Но также учтите, что если по какой-то причине вам нужны изменяемые типы, используйте AtomicInteger
AtomicBoolean
и т. д. Почему Atomic
? Потому что, вводя изменчивость, вы вводили необходимость в безопасности потоков. В чем бы вы не нуждались, если бы ваши типы оставались неизменными, поэтому при использовании изменяемых типов вы также должны заплатить за размышления о безопасности потоков и использование типов из concurrent
пакет. Добро пожаловать в удивительный мир параллельного программирования.
Также для Boolean
- Я призываю вас назвать одну операцию, которую вы, возможно, захотите выполнить, которая заботится о том, является ли Boolean изменяемым. установить в истину? использование myBool = true
, Это переназначение, а не мутация. Отрицание? myBool = !myBool
, То же правило. Обратите внимание, что неизменность - это функция, а не ограничение, поэтому, если вы можете предложить ее, вы должны - и в этих случаях, конечно, можете.
Обратите внимание, что это относится и к другим типам. Самая тонкая вещь с целыми числами count++
но это просто count = count + 1
, если вы не заботитесь о получении значения атомарно... в этом случае используйте изменяемый AtomicInteger
,
Первый основной вариант использования атомарных операций (иногда инкапсулированных) в любом языке - это семантика сравнения и обмена - фундаментальные строительные блоки параллельных приложений.
Второе основное использование - скрыть сложность правильного размещения ограждений памяти, необходимых для семантики модели памяти.
В Java Atomic * инкапсулирует оба вышеперечисленных, первый - с помощью собственного кода платформы, а второй - с помощью volatile
ключевое слово.