共计 3322 个字符,预计需要花费 9 分钟才能阅读完成。
Java 中 long 和 double 的原子性
java 中根本类型中,long 和 double 的长度都是 8 个字节,32 位(4 字节)处理器对其读写操作无奈一次实现,那么,JVM,long 和 double 是原子性的吗?
JVM 中对 long 的操作是不是原子操作?
首先,通过一段程序对 long 的原子性进行判断。测试程序如下:
public class LongAtomTest implements Runnable {
private static long field = 0;
private volatile long value;
public long getValue() {return value;}
public void setValue(long value) {this.value = value;}
public LongAtomTest(long value) {this.setValue(value);
}
@Override
public void run() {
int i = 0;
while (i < 100000) {LongAtomTest.field = this.getValue();
i++;
long temp = LongAtomTest.field;
if (temp != 1L && temp != -1L) {System.out.println("呈现谬误后果" + temp);
System.exit(0);
}
}
System.out.println("运行正确");
}
public static void main(String[] args) throws InterruptedException {
// 获取并打印以后 JVM 是 32 位还是 64 位的
String arch = System.getProperty("sun.arch.data.model");
System.out.println(arch+"-bit");
LongAtomTest t1 = new LongAtomTest(1);
LongAtomTest t2 = new LongAtomTest(-1);
Thread T1 = new Thread(t1);
Thread T2 = new Thread(t2);
T1.start();
T2.start();
T1.join();
T2.join();}
}
能够看到,程序中有两条线程 t1,t2;t1,t2 各自不停的给 long 类型的动态变量 field 赋值为 1,-1;t1,t2 每次赋值后,会读取 field 的值,若 field 值既不是 1 又不是 -1,就将 field 的值打印进去。
如果对 long 的写入和读取操作是原子性的,那么,field 的值只可能是 1 或者 -1
运行后果如下:
32-bit
呈现谬误后果 -4294967295
运行正确
能够看出,当线程 t1,t2 同时对 long 进行写的时候,long 呈现了既不是 t1 写入的值,又不是 t2 写入的值。
能够揣测,jvm 中对 long 的操作并非原子操作。
为什么对 long 的操作不是原子的?
JVM 内存模型中定义了 8 中原子操作:
1.lock: 将一个变量标识为被一个线程独占状态
2.unclock: 将一个变量从独占状态释放出来,开释后的变量才能够被其余线程锁定
3.read: 将一个变量的值从主内存传输到工作内存中,以便随后的 load 操作
4.load: 把 read 操作从主内存中失去的变量值放入工作内存的变量的正本中
5.use: 把工作内存中的一个变量的值传给执行引擎,每当虚拟机遇到一个应用到变量的指令时都会应用该指令
6.assign: 把一个从执行引擎接管到的值赋给工作内存中的变量,每当虚拟机遇到一个给变量赋值的指令时,都要应用该操作
7.store: 把工作内存中的一个变量的值传递给主内存,以便随后的 write 操作
8.write: 把 store 操作从工作内存中失去的变量的值写到主内存中的变量
其中,与赋值,取值相干的包含 read,load,use,assign,store,write
依照这个规定,long 的读写都是原子操作,与咱们的实际后果相同,为什会导致这种问题呢?
对于 32 位操作系统来说,单次次操作能解决的最长长度为 32bit,而 long 类型 8 字节 64bit,所以对 long 的读写都要两条指令能力实现(即每次读写 64bit 中的 32bit)。
如果 JVM 要保障 long 和 double 读写的原子性,势必要做额定的解决。那么,JVM 有对这一状况进行额定解决吗?
针对这一问题能够参考 Java 语言标准文档:jls-17 Non-Atomic Treatment of double and long
- 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.
- 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.
从规定中咱们能够晓得:
1. 对于 64 位的 long 和 double,如果没有被 volatile 润饰,那么对其操作能够不是原子的。在操作的时候,能够分成两步,每次对 32 位操作。
2. 如果应用 volatile 润饰 long 和 double,那么其读写都是原子操作
3. 对于 64 位的援用地址的读写,都是原子操作
4. 在实现 JVM 时,能够自由选择是否把读写 long 和 double 作为原子操作
5. 举荐 JVM 实现为原子操作
从程序失去的后果来看,32 位的 HotSpot 没有把 long 和 double 的读写实现为原子操作。在读写的时候,分成两次操作,每次读写 32 位。因为采纳了这种策略,所以 64 位的 long 和 double 的读与写都不是原子操作。
在硬件,操作系统,JVM 都是 64 位的状况下呢?
对于 64bit 的环境来说,单次操作能够操作 64bit 的数据,即能够以一次性读写 long 或 double 的整个 64bit。因而咱们能够猜想,在 64 位的环境下,long 和 double 的读写有可能是原子操作。在换了 64 位的 JVM 之后,屡次运行,后果都是正确的
64-bit
运行正确
运行正确
结果表明,在 64bit 的虚拟机下,long 的解决是原子性的。
作者 | LouisWong
起源 | https://my.oschina.net/u/1753…