我正在浏览volatile关键字,阅读它,在那里我读到volatile关键字保证可见性而不是原子性,现在可见性是一个线程中的更改立即可见另一个线程,所以为什么我们需要使用atomic Integer或者原子布尔...它是什么需要它,有人可以解释我一个常见的例子使用volatile,以及可见性和原子性的差异
答案 0 :(得分:3)
定义
如果任何其他线程认为它完全发生或根本没有发生,则操作是 atomic 。
一个操作可见到另一个线程,该线程认为它已经发生。
原子性有用性的典型例子是将钱存入银行账户的操作:
synchronized void deposit(int dollars) {
balance = balance + dollars;
}
如果该操作不是原子操作,那么想要同时将钱存入同一账户的两个线程(T1和T2)可以按如下方式执行
T1 reads balance and adds dollars
T2 reads balance and adds dollars
T1 writes the result to balance
T2 writes the result to balance
这是不正确的,因为T1存入的钱无助于余额。
可见性有用性的典型示例是将信息从一个线程传递到另一个线程。例如,用户界面线程可能会将命令传递给后台线程。
易变的含义
volatile使得写入(或读取)变量原子,即使它们是long
或double
类型。
随后从该变量读取的所有线程都可以看到写入volatile变量。
也就是说,我们可以使用volatile将信息传递给另一个线程,例如:
volatile boolean shouldBeRunning = true;
void stop() { // invoked by T1
shouldBeRunning = false;
}
void run() { // invoked by T2
while (shouldBeRunning) {
doWork();
}
}
AtomicBoolean的优势
正如我们上面所看到的,我们可以通过声明boolean
来写入(或读取)volatile
原子。但是,如果我们想要进行更大的操作,例如
void pauseOrResume() {
paused = !paused;
}
原子,声明paused
volatile是不够的,因为两个线程T1和T2可以同时执行pauseOrResume:
T1 reads paused (false) and negates it (true)
T2 reads paused (false) and negates it (true)
T1 writes its result (true)
T2 writes its result (true)
使用原子布尔值,我们可以防止这种情况发生:
void pauseOrResume() {
boolean pausedBefore = paused.get();
if (!paused.compareAndSet(pausedBefore, !pausedBefore)) {
pauseOrResume();
}
}
答案 1 :(得分:1)
让我们分别讨论这两个概念。
<强>可见性强>
保证变量对变量的所有线程都是可见的。可见性可防止缓存变量
的本地副本的线程<强>原子性强>
对可变的变化是原子的,一次发生或根本不发生。在所有操作完成之前,线程不可能看到变量。
需要可见性但JLS自动保证原子性的示例:
private static volatile boolean run;
public static void main(final String[] args) throws Exception {
final ExecutorService executorService = Executors.newSingleThreadExecutor();
executorService.submit(new Worker());
TimeUnit.MINUTES.sleep(1);
run = false;
executorService.awaitTermination(1, TimeUnit.DAYS);
}
private static final class Worker implements Runnable {
@Override
public void run() {
while (run) {
try {
//do some long running task
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException ex) {
//oh well
}
System.out.println("Working really hard");
}
}
}
我们在这里使用run
作为volatile boolean
标志。我们要求运行Worker
的线程看到对boolean
的值的更改,但由于assignemnt已经是原子的,我们不需要以任何方式进行同步。
可见度不足的原因,还需要原子性:
private static volatile boolean run;
private static AtomicInteger counter = new AtomicInteger(0);
public static void main(final String[] args) throws Exception {
final ExecutorService executorService = Executors.newCachedThreadPool();
run = true;
executorService.submit(new Decrementor());
executorService.submit(new Incremetror());
TimeUnit.MINUTES.sleep(1);
run = false;
executorService.awaitTermination(1, TimeUnit.DAYS);
}
private static final class Incremetror implements Runnable {
@Override
public void run() {
while (run) {
while (counter.get() < 10) {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException ex) {
//oh well
}
System.out.println(counter.incrementAndGet());
}
}
}
}
private static final class Decrementor implements Runnable {
@Override
public void run() {
while (run) {
while (counter.get() >= 10) {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException ex) {
//oh well
}
System.out.println(counter.decrementAndGet());
}
}
}
}
这个有点人为的例子显示了两个读取设置计数器变量的线程。线程执行一些长时间运行的任务然后设置计数器。一旦第一个线程将计数器取为10,则第二个线程开始递减它。结果是计数器通常在9到10之间切换,但是由于线程调度,计数器可能会被第二个线程一直递减。
该示例使用忙等待来强调对原子性的需求。这绝不应该在实践中使用。
重点是i++
不是原子。它涉及读取,分配和写入。这里变量的可见性不足以保证可以看到正确的值。另一个线程可以在上述任何操作之间看到变量。
在这里你可以使用synchroized
块,但这非常昂贵。这就是Java AtomicXXX
结构的用武之地。
答案 2 :(得分:0)
AtomicInterger
其他Atomic...
类保证原子性。即它保证变量不能被2个或多个线程同时更改,例如这个代码不是线程安全的,它有race condition
个问题:
private volatile int a;
private void myMethod() {
a++;
}
因为线程A可以读取a
的值并且它将为0,所以线程B读取a
的值 - 它也等于0.但是两个线程都会递增此读取值并且它将保持值1
,但应为2
。
即。原子性保证read-change-write
操作将正确完成,而volatil
仅保证&#34;读取&#34;操作将正确完成。