易失性布尔与AtomicBoolean

时间:2010-09-24 12:01:24

标签: java concurrency boolean volatile atomicboolean

一个易失性布尔无法实现的AtomicBoolean做什么?

11 个答案:

答案 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*对象,可以保证每个方法都是“原子的”。

因此,如果您使用AtomicIntegergetAndAdd(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执行compareAndSetgetAndSet作为原子操作(除非您同步它)。

答案 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. 写入布尔值。
  3. 如果其他线程修改了#12#之间的值,则可能会得到错误的结果。 AtomicBoolean方法通过原子方式执行#1#2步骤来避免此问题。

答案 10 :(得分:-1)

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