Volatile boolean vs AtomicBoolean

AtomicBoolean做了哪些volatile boolean不能做到的事情?

124563 次浏览

你不能将compareAndSetgetAndSet作为volatile boolean的原子操作(当然除非你同步它)。

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

它们完全不同。下面是一个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*对象,可以保证每个方法都是“原子的”。

因此,如果你使用AtomicIntegergetAndAdd(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提示编译器在优化时要更加小心。

我使用volatile字段时,该字段是ONLY UPDATED由它的所有者线程和值只被其他线程读取,你可以把它看作是一个发布/订阅的场景,其中有许多观察者,但只有一个发布者。然而,如果这些观察者必须根据字段的值执行一些逻辑,然后推回一个新值,那么我就使用Atomic* vars或锁或同步块,任何最适合我的。在许多并发场景中,它可以归结为获取值,将其与另一个值进行比较,并在必要时进行更新,因此在Atomic*类中出现了compareAndSet和getAndSet方法。

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

记住成语——

READ - MODIFY- WRITE这个你不能用volatile实现

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

将变量设置为volatile将防止线程将变量的副本保存在线程本地缓存中。

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

AtomicBoolean有一些方法可以原子地执行它们的复合操作,而不必使用synchronized块。另一方面,volatile boolean只能在synchronized块中执行复合操作。

读/写volatile boolean的记忆效果分别与AtomicBooleangetset方法相同。

例如,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设置为true后再次设置回false)。

布尔基元类型是原子的,用于写和读操作,volatile保证happens before原则。因此,如果您需要一个简单的get()和set(),那么您不需要AtomicBoolean。

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

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

然而,如果你有多个线程修改布尔值,你应该使用AtomicBoolean。否则,以下代码是不安全的:

boolean r = !myVolatileBoolean;

该操作分两步完成:

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

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

Volatile boolean vs AtomicBoolean

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 boolean不能做到的事情?

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

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

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

然而,下面的代码将在多线程环境中安全工作,没有锁:

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

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

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

这里的很多答案都过于复杂,令人困惑,或者是错误的。例如:

如果你有多个线程修改布尔值,你应该使用AtomicBoolean

一般来说,这是不正确的。

如果一个变量是volatile,那么对它的每个原子访问都是同步的……

这是不正确的;同步完全是另一回事。

简单的答案是AtomicBoolean允许你在某些操作中防止竞争条件,这些操作需要读取值,然后根据所读取的内容写入一个值;它使这些操作具有原子性(即它删除了变量可能在读和写之间发生变化的竞态条件)——因此得名。

如果你只是读写变量,其中写操作并不依赖于你刚刚读取的值,volatile将很好地工作,即使是多线程。