为什么 AtomicBoolean 不能替代 Boolean?

Why can't AtomicBoolean be a replacement for Boolean?

A​​tomicBoolean 的 Oracle JDK Javadoc 指出:

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

A boolean value that may be updated atomically. See the java.util.concurrent.atomic package specification for description of the properties of atomic variables. An AtomicBoolean is used in applications such as atomically updated flags, and cannot be used as a replacement for a Boolean.

我和一位同事试图找出一个 AtomicBoolean 不能替代的用例,我们唯一能想到的是 Boolean 对象有一些 AtomicBoolean 没有的方法。

这是唯一的原因还是在写这篇文章时有其他想法?

Boolean 是原始 boolean 周围的包装器 class。它可以由编译器从 boolean 自动创建(装箱转换)或转换为布尔值(拆箱转换)。 AtomicBoolean 不是这种情况,它是为并发目的而设计的单独 class。

因此,这两个 classes 在语言级别具有不同的语义:

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

它们不可自动装箱,因此不能用于条件语句,例如,

// Explodey
if (someAtomicBoolean) {
}

布尔值是一个不可变的值对象。它被设计成不变的,并最终确定以强制执行。 java.lang.Boolean 自 1.0 以来一直存在。

A​​tomicBoolean 是可变的,旨在进行更新,以便更新后的值在线程中可见。 AtomicBoolean 是在 Java 5.

中引入的

这些是完全不同的概念,这就是为什么 AtomicBoolean 不是为了扩展 Boolean 而设计的。在不破坏使用它的代码的预期不变性的情况下,您不能用可变对象替换不可变对象。如果原子版本可以在其位置传入,则期望接收不可变值的代码可能会被破坏。

所以这是一个用例:如果 AtomicBoolean 被引入为可以替代 Boolean 的东西,你可能会遇到这样一种情况,在此更改之前创建的 class 可以合理地期望在某些方法中 returns 一个布尔值,因为布尔值是不可变的,所以不需要传递防御副本。如果返回的引用恰好是从更改为使用 AtomicBoolean 而不是 Boolean 的源初始化的,那么该字段现在可以通过调用返回 Boolean 的方法的事物进行修改,方法是将其强制转换为 AtomicBoolean。

原子 classes 设计用于并发更新(作为对 volatile 的改进),但设计并发代码的最有效方法是使用不可变值。所以注意不要把 AtomicBoolean 误认为 "the Boolean you use when writing multithreaded code".

示例:

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() 要么都被调用,要么被调用 none。

在第二种情况下,如果同时修改 AtomicBoolean 的值,则可以同时调用它们中的任一个或 none。

因为 Boolean 一直存在,并且总是被定义为不可变的,AtomicBoolean 是后来引入的,不能替代 Boolean 因为它的行为和代码不同正确地依赖于 Boolean 的不变性,如果该不变性被破坏,则可能会中断。

请注意,Boolean 不能替代 AtomicBoolean 反之亦然。它们只是在语义上不兼容。

任何语言中原子操作(有时被封装)的第一个主要用例是比较和交换语义——并发应用程序的基本构建块。

第二个主要用途是隐藏正确放置内存栅栏的复杂性,这是内存模型语义所必需的。

在 Java Atomic* 中封装了以上两者,前者使用平台特定的本机代码,后者借助 volatile 关键字。

因为Boolean是不可变的。请参阅:Why Wrapper class like Boolean in java is immutable? 和我的回答:


因为2是2,明天不会是3

不可变始终是默认值,尤其是在多线程情况下,它使代码更易于阅读和维护。例证:Java Date API,它充满了设计缺陷。如果 Date 是不可变的,那么 API 将会非常精简。我会知道 Date 操作会创建新日期,并且永远不必寻找修改它们的 APIs。

阅读并发实践以了解不可变类型的真正重要性。

但还要注意,如果出于某种原因您需要可变类型,请使用 AtomicInteger AtomicBoolean 等。为什么 Atomic?因为通过引入可变性,您引入了对线程安全的需求。如果您的类型保持不变,您就不需要它,因此在使用可变类型时,您还必须为考虑线程安全和使用 concurrent 包中的类型付出代价。欢迎来到并发编程的精彩世界。

此外,对于 Boolean - 我要求您命名一个您可能想要执行的操作,该操作关心布尔值是否可变。设置为真?使用 myBool = true。那是重新分配,而不是突变。否定? myBool = !myBool。同样的规则。请注意,不变性是一个特性,而不是一个约束,所以如果你可以提供它,你应该 - 在这些情况下,你当然可以.

请注意,这也适用于其他类型。整数最微妙的事情是 count++,但那只是 count = count + 1,除非你关心以原子方式获取值......在这种情况下使用可变 AtomicInteger.