AtomicBoolean 做了哪些 volatile 布尔值无法实现的功能?
11 回答
当所述字段仅由其所有者线程更新并且该值仅由其他线程读取时,我使用易失性字段,您可以将其视为发布/订阅场景,其中有许多观察者但只有一个发布者。但是,如果这些观察者必须根据字段的值执行一些逻辑,然后推回一个新值,那么我会使用 Atomic* vars 或锁或同步块,只要最适合我。在许多并发场景中,它归结为获取值,将其与另一个值进行比较并在必要时进行更新,因此 Atomic* 类中存在 compareAndSet 和 getAndSet 方法。
检查java.util.concurrent.atomic包的 JavaDocs 以获取 Atomic 类的列表以及它们如何工作的出色解释(刚刚了解到它们是无锁的,因此它们比锁或同步块具有优势)
他们只是完全不同。考虑这个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*
对象,可以保证每个方法都是“原子的”。
因此,如果您使用AtomicInteger
and 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
提示编译器在优化时要更加小心。
你不能做compareAndSet
,getAndSet
作为与 volatile boolean 的原子操作(当然除非你同步它)。
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();
}
保证只通知侦听器一次(假设没有其他线程在将其设置为AtomicBoolean
后false
再次将其设置为true
)。
volatile
关键字保证共享该变量的线程之间的happens-before关系。它不能保证 2 个或更多线程在访问该布尔变量时不会相互中断。
易失性布尔值与原子布尔值
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 字段是从对象角度封装关键共享资源的好方法。这意味着开发人员不能只处理该字段,假设它不共享,可能会为字段++注入问题;或其他引入竞争条件的代码。
如果有多个线程访问类级别变量,则每个线程都可以在其线程本地缓存中保留该变量的副本。
使变量 volatile 将阻止线程将变量的副本保留在 threadlocal 缓存中。
原子变量是不同的,它们允许对它们的值进行原子修改。
Boolean 原始类型对于写入和读取操作是原子的,volatile 保证了先发生原则。所以如果你需要一个简单的 get() 和 set() 那么你就不需要 AtomicBoolean。
另一方面,如果您需要在设置变量的值之前进行一些检查,例如“如果为真则设置为假”,那么您也需要原子地执行此操作,在这种情况下使用 compareAndSet 和其他方法提供AtomicBoolean,因为如果您尝试使用 volatile boolean 实现此逻辑,则需要进行一些同步以确保值在 get 和 set 之间没有改变。
记住成语-
READ - MODIFY- WRITE 这是您无法使用 volatile 实现的
如果你只有一个线程修改你的布尔值,你可以使用一个 volatile 布尔值(通常你这样做是为了定义一个stop
在线程的主循环中检查的变量)。
但是,如果您有多个线程修改布尔值,则应使用AtomicBoolean
. 否则,以下代码不安全:
boolean r = !myVolatileBoolean;
此操作分两步完成:
- 读取布尔值。
- 写入布尔值。
如果其他线程修改 和 之间的值#1
,2#
您可能会得到错误的结果。方法通过执行步骤和原子地AtomicBoolean
避免这个问题。#1
#2
两者具有相同的概念,但在原子布尔值中,它将为操作提供原子性,以防 CPU 切换发生在两者之间。