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