繁体   English   中英

易失性 boolean 与 AtomicBoolean

[英]Volatile boolean vs AtomicBoolean

易失性 boolean 无法实现的 AtomicBoolean 做了什么?

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

检查java.util.concurrent.atomic包的 JavaDocs 以获取 Atomic 类列表以及它们如何工作的出色解释(刚刚了解到它们是无锁的,因此它们比锁或同步块有优势)

他们只是完全不同。 考虑这个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*对象,可以保证每个方法都是“原子的”。

因此,如果您使用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提示编译器在优化时要更加小心。

您不能使用 volatile 布尔值将compareAndSetgetAndSet作为原子操作(除非您同步它)。

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

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

例如, 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设置为true后再次将其设置回false )。

volatile关键字保证共享该变量的线程之间发生先发生的关系。 它不能保证 2 个或更多线程在访问该布尔变量时不会相互中断。

挥发性布尔值与原子布尔值

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()以及无需锁定即可实现多个操作(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* 类包装 volatile 字段是从对象的角度封装关键共享资源的好方法。 这意味着开发人员不能假设它没有被共享可能会注入字段 ++ 的问题。 或其他引入竞争条件的代码。

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

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

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

布尔基元类型对于写和读操作是原子的,易失性保证了先发生原则。 所以如果你需要一个简单的 get() 和 set() 那么你就不需要 AtomicBoolean。

另一方面,如果您需要在设置变量值之前进行一些检查,例如“如果为真则设置为假”,那么您也需要以原子方式执行此操作,在这种情况下,请使用 compareAndSet 和其他提供的方法AtomicBoolean,因为如果您尝试使用 volatile boolean 实现此逻辑,您将需要一些同步以确保值在 get 和 set 之间没有更改。

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

但是,如果您有多个线程修改布尔值,则应使用AtomicBoolean 否则,以下代码是不安全的:

boolean r = !myVolatileBoolean;

此操作分两步完成:

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

如果其他线程修改了#12#之间的值,您可能会得到错误的结果。 AtomicBoolean方法通过原子地执行步骤#1#2来避免这个问题。

记住成语 -

读取 - 修改 - 写入这是您无法使用 volatile 实现的

这里的很多答案都过于复杂、令人困惑或只是错误的。 例如:

…如果您有多个线程修改 boolean,您应该使用AtomicBoolean

作为一般性陈述,这是不正确的。

如果一个变量是 volatile 的,对它的每个原子访问都是同步的……

这是不正确的; 同步完全是另一回事。

简单的答案是AtomicBoolean允许您在某些需要读取值然后写入取决于您读取的内容的值的操作中防止竞争条件; 它使此类操作具有原子性(即,它消除了变量可能在读取和写入之间发生变化的竞争条件)——因此得名。

如果您只是在读取和写入不依赖于您刚刚读取的值的变量,那么即使使用多个线程, volatile也可以正常工作。

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM