java - 为什么使用 volatile 使 long 和 double atomic

标签 java multithreading concurrency volatile

<分区>

我正在尝试学习 Java 中多线程中使用的术语。因此,如果我在以下文本中使用了错误的定义,请纠正我:

我从不同资源中得到的发现

原子操作: 根据 Java 文档:

In programming, an atomic action is one that effectively happens all at once. An atomic action cannot stop in the middle: it either happens completely, or it doesn't happen at all. No side effects of an atomic action are visible until the action is complete.

这就是为什么读取或写入 long 或 double 变量不是原子的。因为它涉及两个操作,第一个 32 位和第二个 32 位读/写变量。另外,从上面的段落中,我了解到如果我在一个方法上使用 synchronized,它会使该方法成为原子的(理论上来说)。

Volatile 变量: 同样来自 Java Doc:

This means that changes to a volatile variable are always visible to other threads. What's more, it also means that when a thread reads a volatile variable, it sees not just the latest change to the volatile, but also the side effects of the code that led up the change.

现在,同样根据 Joshua Bloch 的 Effective Java 2nd Edition,考虑书中关于 volatile 声明的以下几点:

考虑以下几点:

// Broken - requires synchronization!
private static volatile int nextSerialNumber = 0;

public static int generateSerialNumber() {
    return nextSerialNumber++;
}

The method’s state consists of a single atomically accessible field, nextSerialNumber, and all possible values of this field are legal. Therefore, no synchronization is necessary to protect its invariants. Still, the method won’t work properly without synchronization.

这是因为 nextSerialNumber++ 不是原子的,因为它执行读取、递增、写入。

我的总结

所以如果 nextSerialNumber++ 不是原子的,并且需要 synchronize。那为什么下面是原子的,不需要同步呢?

private static volatile long someNumber = 0;

public static int setNumber(long someNumber) {
    return this.someNumber = someNumber;
}

我不明白的是为什么在 doublelong 上使用 volatile 使其成为原子?

因为 volatile 所做的就是确保线程 B 是否尝试读取由线程 A 写入的 long 变量,并且只有它的 32 位由线程 A 写入,然后当线程 B 访问该资源时,它将获得线程 A 写入的 32 位数字。这并没有使其成为 atomic 作为术语的定义atomic 在 Java Doc 中有解释。

最佳答案

What I don't understand is why using volatile on double or long, makes it atomic?

如果不使用 volatile 关键字,您可能会读取一个线程写入的 doublelong 的前 32 位,而另一个另一个线程写入的 32 位,称为字撕裂,显然不是原子的。

volatile 关键字确保不会发生这种情况。您读取的 64 位值将是一个线程写入的值,而不是多个线程写入结果的 Franken-value。这就是由于 volatile 关键字,这些类型成为原子的意思。

无论类型(64 位或 32 位)如何,volatile 关键字都不能使像 x++ 这样的操作成为原子操作,因为它是一个复合操作(读取 + 递增 +写),而不是简单的写。 x++ 中涉及的操作可能会被其他线程的操作交错。 volatile 关键字不能使此类复合操作原子化。

So if nextSerialNumber++ is not atomic, and requires synchronize. Then why the following is atomic and doesn't require synchronize?

private static volatile long someNumber = 0;

public static int setNumber(long someNumber) {
    return this.someNumber = someNumber;
}

nextSerialNumber++ 需要同步,因为它是复合操作,因此不是原子操作。

this.someNumber = someNumber 是原子的,因为 this.someNumbervolatile,赋值操作也是原子的,是一个单一的手术。因此不需要同步。如果没有 volatilethis.someNumber 将无法以原子方式编写,因此需要同步。

关于java - 为什么使用 volatile 使 long 和 double atomic,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34387242/

相关文章:

javascript - jQuery/AJAX并发访问全局变量

java - 空的 Runnable 类需要多少内存?

java - 使用 sendBroadcast 并执行操作 "android.rfid.INPUT"

Objective-C/Cocoa 等效于 C# ManualResetEvent

c# - 序列化 XAML 的 ConcurrentBag

for-loop - go 中循环和 goroutinues 的意外行为

java - 为什么在 Java 中使用 ByteArrayInputStream 而不是 byte[]

java - 如何找到任意日期最近的工作日?

java - 如何与消耗整个队列的消费者保持阻塞队列的同步

c - 多线程c exe崩溃但不在vs2013调试中