这个 64 位数字变量多线程规则在 Java 1.8 中仍然适用还是已经过时了?

Is this 64-bit numeric variable multi-threading rule still true in Java 1.8 or is it out-of-date?

来自 Goetz、Peierls、Bloch 等人。 2006 年:Java 并发实践

3.1.2. Nonatomic 64-bit Operations

When a thread reads a variable without synchronization, it may see a stale value, but at least it sees a value that was actually placed there by some thread rather than some random value. This safety guarantee is called out-of-thin-air safety.

Out-of-thin-air safety applies to all variables, with one exception: 64-bit numeric variables ( double and long ) that are not declared volatile (see Section 3.1.4 ). The Java Memory Model requires fetch and store operations to be atomic, but for nonvolatile long and double variables, the JVM is permitted to treat a 64-bit read or write as two separate 32-bit operations. If the reads and writes occur in different threads, it is therefore possible to read a nonvolatile long and get back the high 32 bits of one value and the low 32 bits of another.[3]

Thus, even if you don't care about stale values, it is not safe to use shared mutable long and double variables in multithreaded programs unless they are declared volatile or guarded by a lock.

[3] When the Java Virtual Machine Specification was written, many widely used processor architectures could not efficiently provide atomic 64-bit arithmetic operations.

这是在 2004 年发布 Java 5 之后编写的,其中有许多更改旨在更轻松地进行多线程和并发编程。那为什么它仍然适用呢?即使是十年后的现在?

如果只是因为可以在 32 位硬件上 运行 Java 应用程序,为什么没有 JVM 运行-time 选项允许它,如果想要吗?

能够编写多线程和低延迟应用程序代码而无需担心这个陷阱,这不是很有好处吗?

Is this 64-bit numeric variable multi-threading rule still true in Java 1.8 or is it out-of-date?

对,还是这样,参考Java8规范:
https://docs.oracle.com/javase/specs/jls/se8/html/jls-17.html#jls-17.7

For the purposes of the Java programming language memory model, a single write to a non-volatile long or double value is treated as two separate writes: one to each 32-bit half. This can result in a situation where a thread sees the first 32 bits of a 64-bit value from one write, and the second 32 bits from another write.

Writes and reads of volatile long and double values are always atomic.

Writes to and reads of references are always atomic, regardless of whether they are implemented as 32-bit or 64-bit values.


So why does it still apply?

规范中也有解释:
https://docs.oracle.com/javase/specs/jls/se8/html/jls-17.html#jls-17.7

Some implementations may find it convenient to divide a single write action on a 64-bit long or double value into two write actions on adjacent 32-bit values. For efficiency's sake, this behavior is implementation-specific; an implementation of the Java Virtual Machine is free to perform writes to long and double values atomically or in two parts.

Implementations of the Java Virtual Machine are encouraged to avoid splitting 64-bit values where possible. Programmers are encouraged to declare shared 64-bit values as volatile or synchronize their programs correctly to avoid possible complications.

我认为这是因为 Java 在许多硬件型号上是 运行 - 例如手机、路由器,甚至可能在冰箱、洗衣机、吸尘器等上,这可以是配备微型 8 位或 16 位微处理器。并且 Java 规范对所有这些都是通用的。