易失性布尔与AtomicBoolean

问题描述 投票:210回答:10

AtomicBoolean做什么,一个volatile布尔无法实现?

java concurrency boolean volatile atomicboolean
10个回答
85
投票

它们完全不同。考虑这个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,你可能会遇到一个无限循环,因为第一个线程可能会缓存stop的值。在这里,volatile作为编译器的一个暗示,在优化时会更加小心。


2
投票

如果你只有一个线程修改你的布尔值,你可以使用volatile布尔值(通常你这样做来定义在线程的主循环中检查的AtomicBoolean变量)。

但是,如果您有多个线程修改布尔值,则应使用boolean r = !myVolatileBoolean; 。否则,以下代码不安全:

#1

此操作分两步完成:

  1. 读取布尔值。
  2. 写入布尔值。

如果另一个线程修改2#AtomicBoolean之间的值,则可能会得到错误的结果。 #1方法通过原子方式执行#2和qazxswpoi步骤来避免这个问题。


242
投票

当所述字段仅由其所有者线程更新时我使用volatile字段并且该值仅由其他线程读取,您可以将其视为发布/订阅场景,其中有许多观察者但只有一个发布者。但是,如果这些观察者必须根据字段的值执行一些逻辑,然后推回一个新的值,那么我会使用Atomic * vars或锁或同步块,这些都适合我。在许多并发场景中,它归结为获取值,将其与另一个值进行比较并在必要时进行更新,因此在Atomic *类中存在compareAndSet和getAndSet方法。

检查java.util.concurrent.atomic软件包的JavaDocs以获取Atomic类的列表以及它们如何工作的优秀解释(只是了解到它们是无锁的,因此它们优于锁或同步块)


51
投票

你不能用compareAndSetgetAndSet作为具有volatile布尔值的原子操作(当然除非你同步它)。


40
投票

AtomicBoolean具有以原子方式执行其复合操作的方法,而无需使用synchronized块。另一方面,volatile boolean只能在synchronized区块内执行复合操作。

读/写volatile boolean的记忆效应分别与getsetAtomicBoolean方法相同。

例如,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)。


14
投票

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个或更多线程不会互相中断。


5
投票

如果有多个线程访问类级别变量,则每个线程可以在其threadlocal缓存中保留该变量的副本。

使变量volatile将阻止线程在threadlocal缓存中保留变量的副本。

原子变量是不同的,它们允许对其值进行原子修改。


5
投票

易失性布尔与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 *类包装易失性字段是从对象角度封装关键共享资源的好方法。这意味着开发人员不能只处理该字段,假设它没有共享可能注入字段++的问题;或引入竞争条件的其他代码。


4
投票

布尔基元类型对于写入和读取操作是原子的,volatile保证了先发生原则。因此,如果您需要一个简单的get()和set(),那么您不需要AtomicBoolean。

另一方面,如果您需要在设置变量值之前执行某些检查,例如“如果为true,则设置为false”,那么你需要原子地执行此操作,在这种情况下使用compareAndSet和AtomicBoolean提供的其他方法,因为如果你尝试使用volatile boolean实现这个逻辑,你需要一些同步到确保get和set之间的值没有变化。


3
投票

记住IDIOM -

READ - MODIFY- WRITE这是你用volatile来实现的

© www.soinside.com 2019 - 2024. All rights reserved.