挥发性布尔值与AtomicBoolean


244

可变布尔无法实现的AtomicBoolean有什么作用?


16
我一直在寻找一个更细微的答案:“每个都有什么局限性?”。例如,如果is是由一个线程设置并由一个或多个其他线程读取的标志,则不需要AtomicBoolean。但是,正如我从这些答案中看到的那样,如果线程在多个线程中共享一个变量,这些线程可以写入并根据其读取结果起作用,则AtomicBoolean使CAS类型的非锁定操作发挥作用。实际上,我在这里学到了很多东西。希望其他人也能从中受益。
JeffV


volatile布尔值将需要显式同步以处理竞争条件,换句话说,诸如共享资源被多个线程(例如,递增/递减计数器或布尔值翻转)更新的情况(状态更改)。
sactiw

Answers:


99

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

因此,如果使用AtomicIntegerand getAndAdd(int delta),则可以确保结果为10。以同样的方式,如果两个线程同时对一个boolean变量AtomicBoolean求反,则使用a可以确保它之后具有原始值,而使用a volatile boolean则不能。

因此,只要您有多个线程修改一个字段,就需要使其成为原子的或使用显式同步。

的目的volatile是不同的。考虑这个例子

volatile boolean stop = false;
void loop() {
    while (!stop) { ... }
}
void stop() { stop = true; }

如果您正在运行一个线程loop()而另一个线程正在调用stop(),则如果您忽略它volatile,则可能会陷入无限循环,因为第一个线程可能会缓存stop的值。在这里,这volatile暗示了编译器在优化时会更加谨慎。


84
-1:您只是在举一些例子,但并没有真正解释volatile和Atomicxxxx之间的区别。
詹森·S

70
问题不关乎volatile。现在的问题是关于volatile booleanVS AtomicBoolean
2012年

26
-1:专门针对布尔值的问题,与其他数据类型相比,布尔值是唯一的情况,应直接进行解释。
约翰·哈格

8
@ sgp15它与同步做,因为Java 5中的
单向的男子

6
如果布尔值由许多线程读取,但仅由一个线程写入,则volatile boolean足够。如果作家很多,那么您可能需要AtomicBoolean
StvnBrkdll

263

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

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


1
@ksl我认为@teto想描述一下,如果只有一个线程修改booleanvar,则应该选择volatile boolean
znlyj

2
优秀的总结。
拉文德拉·布布

56

你不能这样做compareAndSetgetAndSet因为挥发性布尔原子操作(当然,除非你进行同步)。


6
确实如此,但这不是布尔值的罕见要求吗?
罗宾2014年

1
@Robin考虑使用它来控制初始化方法的延迟调用。
Ustaman Sangat

实际上,我认为这是主要用例之一。
fool4jesus

42

AtomicBoolean有一些方法可以自动执行其复合操作,而不必使用synchronized块。另一方面,volatile boolean仅在synchronized块内执行复合操作才可以执行。

读/写to的记忆效应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();
}

确保只通知一次侦听器(假设其他线程在设置为之后都不会再设置为AtomicBooleanback )。falsetrue


14

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


14
布尔(如原始类型)访问在Java中是原子的。阅读和作业。因此,没有其他线程会“中断”布尔操作。
MaciejBiłas2011年

1
抱歉,这如何回答问题?一个Atomic*类包装一个volatile字段。
灰色

CPU缓存不是设置易失性的主要因素吗?为了确保实际读取的值是最新设置的值
jocull

8

挥发性布尔值与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 *,那么您也可能只拥有一个volatile字段。

可变布尔无法实现的AtomicBoolean有什么作用?

原子*类为您提供了更高级的功能,如方法incrementAndGet()compareAndSet()以及其他无锁实现多个操作(GET /递增/集,测试/套)。这就是Atomic *类如此强大的原因。

例如,如果多个线程使用,则下面的代码++将存在竞争条件,因为++实际上是:get,increment和set。

private volatile value;
...
// race conditions here
value++;

但是,以下代码将在没有锁的情况下安全地在多线程环境中运行:

private final AtomicLong value = new AtomicLong();
...
value.incrementAndGet();

同样重要的是要注意,使用Atomic *类包装volatile字段是从对象的角度封装关键共享资源的好方法。这意味着开发人员不能仅仅假设未共享该字段就处理该字段,这可能会给field ++注入问题。或其他引入竞争条件的代码。


5

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

将变量设为volatile将防止线程将变量的副本保留在threadlocal缓存中。

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


4

布尔基元类型对于写和读操作是原子的,易失性保证事前发生原则。因此,如果您需要简单的get()和set(),则不需要AtomicBoolean。

另一方面,如果您需要在设置变量值之前执行一些检查,例如“如果为true,则设置为false”,那么您也需要自动执行此操作,在这种情况下,请使用compareAndSet和其他提供的方法AtomicBoolean,因为如果您尝试使用易失性布尔值来实现此逻辑,则需要进行一些同步以确保该值在get和set之间没有改变。


3

记住IDIOM-

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


2
简短,酥脆并切入要点。volatile仅在所有者线程具有更新字段值的能力而其他线程只能读取的情况下才起作用。
Chaklader Asfak Arefe '18

3

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

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

boolean r = !myVolatileBoolean;

此操作分两个步骤完成:

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

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


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.