AtomicBoolean相对于volatile boolean的优点是什么?
AtomicBoolean相对于volatile boolean的优点是什么?
boolean
变量,我们应该选择volatile boolean
。 - znlyj它们完全不同。考虑这个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
和getAndAdd(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
。问题在于volatile boolean
与AtomicBoolean
之间的区别。 - dolmenvolatile boolean
就足够了。如果有多个写入者,您可能需要使用AtomicBoolean
。 - StvnBrkdll除非你使用同步,否则无法将compareAndSet
和 getAndSet
作为具有原子性操作的volatile boolean
。
AtomicBoolean
提供了一些可以原子性地执行复合操作的方法,而无需使用 synchronized
块。另一方面,只有在 synchronized
块中才能执行 volatile boolean
的复合操作。
读写 volatile boolean
的内存效果与分别调用 AtomicBoolean
的 get
和 set
方法相同。
例如,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 boolean与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无法实现的吗?
Atomic*类提供了更高级的方法,比如数字的incrementAndGet()
、布尔值的compareAndSet()
,以及其他实现多个操作(获取/递增/设置、测试/设置)而无需锁定的方法。这就是Atomic*类如此强大的原因。
例如,如果多个线程使用下面的代码进行递增操作,由于++
实际上是获取、递增和设置操作,所以会产生竞态条件。
private volatile value;
...
// race conditions here
value++;
然而,在多线程环境下,以下代码可以安全地工作而不需要锁:
private final AtomicLong value = new AtomicLong();
...
value.incrementAndGet();
同时需要注意的是,使用Atomic*类来封装易变字段是从对象角度将关键共享资源封装起来的好方法。这意味着开发人员不能仅仅处理该字段,假设它不被共享,可能会通过field ++;或其他引入竞争条件的代码来注入问题。
volatile
关键字确保共享该变量的线程之间存在 happens-before 关系。它不能保证在访问该布尔变量时 2 个或更多线程不会相互中断。
Atomic*
类封装了一个volatile
字段。 - Gray记住这个成语 - 阅读 - 修改 - 写入,使用 volatile 无法实现。
volatile
只在所有者线程有能力更新字段值并且其他线程只能读取的情况下才起作用。 - Arefevolatile boolean keepRunning = true;
,两个不相关的线程可以调用worker上的取消方法来设置keepRunning = false;
,并且工作线程将正确地获取到最新写入的值。唯一无法实现的是类似于keepRunning = !keepRunning;
这样的读取-修改-写入操作。 - FalkreonkeepRunning = true;
。 “没有什么是真实的,一切皆有可能” :) - Falkreon如果有多个线程访问类级变量,每个线程可以在其本地缓存中保存该变量的副本。
将变量设置为volatile将防止线程在本地缓存中保存变量的副本。
原子变量不同,它们允许对其值进行原子修改。
如果只有一个线程修改您的布尔变量,则可以使用volatile布尔变量(通常这样做是为了定义在线程的主循环中检查的“stop”变量)。
但是,如果有多个线程修改该布尔变量,则应使用AtomicBoolean。否则,以下代码是不安全的:
boolean r = !myVolatileBoolean;
这个操作包含两个步骤:
如果在 #1
和 #2
之间,另一个线程修改了该值,则您可能会得到错误的结果。使用 AtomicBoolean
方法可以通过原子地执行步骤 #1
和 #2
来避免此问题。
volatile boolean
可以安全地写入,但不能否定;否定是一种读-修改-写的循环。但只有 myVolatileBool = false;
是线程安全的——因为这就是volatile
的作用,强制任何写操作都要写入同一堆内存,并强制任何读操作来自堆内存。 - Falkreon