288

AtomicBoolean 做了哪些 volatile 布尔值无法实现的功能?

4

11 回答 11

277

当所述字段仅由其所有者线程更新并且该值仅由其他线程读取时,我使用易失性字段,您可以将其视为发布/订阅场景,其中有许多观察者但只有一个发布者。但是,如果这些观察者必须根据字段的值执行一些逻辑,然后推回一个新值,那么我会使用 Atomic* vars 或锁或同步块,只要最适合我。在许多并发场景中,它归结为获取值,将其与另一个值进行比较并在必要时进行更新,因此 Atomic* 类中存在 compareAndSet 和 getAndSet 方法。

检查java.util.concurrent.atomic包的 JavaDocs 以获取 Atomic 类的列表以及它们如何工作的出色解释(刚刚了解到它们是无锁的,因此它们比锁或同步块具有优势)

于 2010-09-24T13:16:47.917 回答
123

他们只是完全不同。考虑这个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 }
}

如果一个变量是 volatile 的,对它的每个原子访问都是同步的,但实际上什么是原子访问并不总是很明显。对于一个Atomic*对象,可以保证每个方法都是“原子的”。

因此,如果您使用AtomicIntegerand getAndAdd(int delta),您可以确定结果将是10。同样,如果两个线程同时否定一个boolean变量,使用 aAtomicBoolean可以确保它之后具有原始值,使用 avolatile boolean则不能。

因此,当您有多个线程修改一个字段时,您需要使其原子化或使用显式同步。

的目的volatile是不同的。考虑这个例子

volatile boolean stop = false;
void loop() {
    while (!stop) { ... }
}
void stop() { stop = true; }

如果您有一个线程正在运行loop()并且另一个线程正在调用stop(),那么如果您省略 ,您可能会遇到无限循环volatile,因为第一个线程可能会缓存 stop 的值。在这里,volatile提示编译器在优化时要更加小心。

于 2010-09-24T12:34:49.090 回答
60

你不能做compareAndSet,getAndSet作为与 volatile boolean 的原子操作(当然除非你同步它)。

于 2010-09-24T12:02:51.267 回答
45

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();
}

保证只通知侦听器一次(假设没有其他线程在将其设置为AtomicBooleanfalse再次将其设置为true)。

于 2012-10-26T16:10:21.520 回答
16

volatile关键字保证共享该变量的线程之间的happens-before关系。它不能保证 2 个或更多线程在访问该布尔变量时不会相互中断。

于 2010-09-24T12:08:58.720 回答
14

易失性布尔值与原子布尔值

Atomic* 类包装了相同类型的 volatile 原语。从来源:

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 做了哪些 volatile 布尔值无法实现的功能?

Atomic* 类为您提供了提供更高级功能的方法,例如incrementAndGet()数字、compareAndSet()布尔值和其他实现多个操作(get/increment/set、test/set)而无需锁定的方法。这就是为什么 Atomic* 类如此强大的原因。

例如,如果多个线程正在使用以下代码 using ++,则会出现竞争条件,因为++实际上是:get、increment 和 set。

private volatile value;
...
// race conditions here
value++;

但是,以下代码将在没有锁的情况下安全地在多线程环境中工作:

private final AtomicLong value = new AtomicLong();
...
value.incrementAndGet();

还需要注意的是,使用 Atomic* 类包装 volatile 字段是从对象角度封装关键共享资源的好方法。这意味着开发人员不能只处理该字段,假设它不共享,可能会为字段++注入问题;或其他引入竞争条件的代码。

于 2016-09-06T14:53:04.520 回答
6

如果有多个线程访问类级别变量,则每个线程都可以在其线程本地缓存中保留该变量的副本。

使变量 volatile 将阻止线程将变量的副本保留在 threadlocal 缓存中。

原子变量是不同的,它们允许对它们的值进行原子修改。

于 2011-12-27T07:36:49.100 回答
5

Boolean 原始类型对于写入和读取操作是原子的,volatile 保证了先发生原则。所以如果你需要一个简单的 get() 和 set() 那么你就不需要 AtomicBoolean。

另一方面,如果您需要在设置变量的值之前进行一些检查,例如“如果为真则设置为假”,那么您也需要原子地执行此操作,在这种情况下使用 compareAndSet 和其他方法提供AtomicBoolean,因为如果您尝试使用 volatile boolean 实现此逻辑,则需要进行一些同步以确保值在 get 和 set 之间没有改变。

于 2013-11-18T10:46:49.527 回答
5

记住成语-

READ - MODIFY- WRITE 这是您无法使用 volatile 实现的

于 2011-09-29T09:51:47.327 回答
5

如果你只有一个线程修改你的布尔值,你可以使用一个 volatile 布尔值(通常你这样做是为了定义一个stop在线程的主循环中检查的变量)。

但是,如果您有多个线程修改布尔值,则应使用AtomicBoolean. 否则,以下代码不安全:

boolean r = !myVolatileBoolean;

此操作分两步完成:

  1. 读取布尔值。
  2. 写入布尔值。

如果其他线程修改 和 之间的值#12#您可能会得到错误的结果。方法通过执行步骤和原子地AtomicBoolean避免这个问题。#1#2

于 2015-11-05T08:58:12.190 回答
-2

两者具有相同的概念,但在原子布尔值中,它将为操作提供原子性,以防 CPU 切换发生在两者之间。

于 2019-12-14T12:37:54.143 回答