AtomicBoolean做了哪些volatile boolean不能做到的事情?
你不能将compareAndSet, getAndSet作为volatile boolean的原子操作(当然除非你同步它)。
compareAndSet
getAndSet
volatile关键字保证共享该变量的线程之间的happens-before关系。它不能保证2个或更多的线程在访问布尔变量时不会相互中断。
volatile
它们完全不同。下面是一个volatile整数的例子:
volatile int i = 0; void incIBy5() { i += 5; }
如果两个线程同时调用函数,i之后可能是5,因为编译后的代码将有点类似于此(除了你不能在int上同步):
i
int
void incIBy5() { int temp; synchronized(i) { temp = i } synchronized(i) { i = temp + 5 } }
如果一个变量是volatile的,那么对它的每个原子访问都是同步的,但是并不总是很明显什么才是原子访问。使用Atomic*对象,可以保证每个方法都是“原子的”。
Atomic*
因此,如果你使用AtomicInteger和getAndAdd(int delta),你可以确定结果将是10。同样地,如果两个线程同时对boolean变量求反,使用AtomicBoolean可以确保它之后具有原始值,而使用volatile boolean则不能。
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,你可能会陷入一个无限循环,因为第一个线程可能会缓存stop的值。这里,volatile提示编译器在优化时要更加小心。
loop()
stop()
我使用volatile字段时,该字段是ONLY UPDATED由它的所有者线程和值只被其他线程读取,你可以把它看作是一个发布/订阅的场景,其中有许多观察者,但只有一个发布者。然而,如果这些观察者必须根据字段的值执行一些逻辑,然后推回一个新值,那么我就使用Atomic* vars或锁或同步块,任何最适合我的。在许多并发场景中,它可以归结为获取值,将其与另一个值进行比较,并在必要时进行更新,因此在Atomic*类中出现了compareAndSet和getAndSet方法。
检查java.util.concurrent.atomic包的JavaDocs,以获得原子类的列表和它们如何工作的出色解释(刚刚了解到它们是无锁的,因此它们比锁或同步块有优势)
记住成语——
READ - MODIFY- WRITE这个你不能用volatile实现
如果有多个线程访问类级别变量那么 每个线程都可以在其线程本地缓存中保留该变量的副本
将变量设置为volatile将防止线程将变量的副本保存在线程本地缓存中。
原子变量是不同的,它们允许对其值进行原子修改。
AtomicBoolean有一些方法可以原子地执行它们的复合操作,而不必使用synchronized块。另一方面,volatile boolean只能在synchronized块中执行复合操作。
synchronized
读/写volatile boolean的记忆效果分别与AtomicBoolean的get和set方法相同。
get
set
例如,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)。
true
false
布尔基元类型是原子的,用于写和读操作,volatile保证happens before原则。因此,如果您需要一个简单的get()和set(),那么您不需要AtomicBoolean。
另一方面,如果你需要在设置一个变量的值之前执行一些检查,例如。"如果为真,则设置为假",那么您也需要原子地执行此操作,在这种情况下使用compareAndSet和AtomicBoolean提供的其他方法,因为如果您尝试使用volatile boolean实现此逻辑,则需要一些同步来确保get和set之间的值没有更改。
如果只有一个线程修改布尔值,则可以使用volatile布尔值(通常这样做是为了在线程的主循环中定义一个stop变量)。
stop
然而,如果你有多个线程修改布尔值,你应该使用AtomicBoolean。否则,以下代码是不安全的:
boolean r = !myVolatileBoolean;
该操作分两步完成:
如果其他线程修改了#1和2#之间的值,则可能会得到错误的结果。AtomicBoolean方法通过原子地执行步骤#1和#2来避免这个问题。
#1
2#
#2
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*,那么你也可以用一个volatile字段来代替。
原子类为您提供了提供更高级功能的方法,例如用于数字的incrementAndGet(),用于布尔值的compareAndSet(),以及其他实现多重操作(get/increment/set, test/set)而无需锁定的方法。这就是为什么Atomic*类如此强大。
incrementAndGet()
compareAndSet()
例如,如果多个线程使用++使用以下代码,则会存在竞态条件,因为++实际上是:get、increment和set。
++
private volatile value; ... // race conditions here value++;
然而,下面的代码将在多线程环境中安全工作,没有锁:
private final AtomicLong value = new AtomicLong(); ... value.incrementAndGet();
同样重要的是要注意,从对象的角度来看,使用Atomic*类包装volatile字段是封装关键共享资源的好方法。这意味着开发人员不能假设字段不共享就处理它,可能会在字段++中注入问题;或其他引入竞态条件的代码。
两者都是相同的概念,但在原子布尔中,它将为操作提供原子性,以防cpu切换发生在两者之间。
这里的很多答案都过于复杂,令人困惑,或者是错误的。例如:
如果你有多个线程修改布尔值,你应该使用AtomicBoolean。
一般来说,这是不正确的。
如果一个变量是volatile,那么对它的每个原子访问都是同步的……
这是不正确的;同步完全是另一回事。
简单的答案是AtomicBoolean允许你在某些操作中防止竞争条件,这些操作需要读取值,然后根据所读取的内容写入一个值;它使这些操作具有原子性(即它删除了变量可能在读和写之间发生变化的竞态条件)——因此得名。
如果你只是读写变量,其中写操作并不依赖于你刚刚读取的值,volatile将很好地工作,即使是多线程。