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