ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
_**Java虚拟机规范定义的许多规则中的一条:所有对基本类型的操作除了某些对long类型和double类型的操作之外,都是原子级的;**_ > 当线程把主存中的 long/double类型的值读到线程内存中时,可能是两次32位值的写操作,显而易见,如果几个线程同时操作,那么就可能会出现高低2个32位值出错的情况发生。即long,double高低位问题,非线程安全 举例说明: 即如有一个long类型的field字段,某个线程正在执行 field = 123L ,而同时有另一个线程正在执行 field = 456L,这样的指定操作之后field的值会是什么,是无法保证的。也许是123L,也可能是456L,或许是0L,甚至还可能是31415926L ### JVM内存模型中定义了8种原子操作 1. lock:将一个变量标识为被一个线程独占状态 2. unclock:将一个变量从独占状态释放出来,释放后的变量才可以被其他线程锁定 3. read:将一个变量的值从主内存传输到工作内存中,以便随后的load操作 4. load:把read操作从主内存中得到的变量值放入工作内存的变量的副本中 5. use:把工作内存中的一个变量的值传给执行引擎,每当虚拟机遇到一个使用到变量的指令时都会使用该指令 6. assign:把一个从执行引擎接收到的值赋给工作内存中的变量,每当虚拟机遇到一个给变量赋值的指令时,都要使用该操作 7. store:把工作内存中的一个变量的值传递给主内存,以便随后的write操作 8. write:把store操作从工作内存中得到的变量的值写到主内存中的变量 对于32位操作系统来说单次次操作能处理的最长长度为32bit,而long类型8字节64bit,所以对long的读写都要两条指令才能完成\(即每次读写64bit中的32bit\);如果JVM要保证long和double读写的原子性,势必要做额外的处理 ``` 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(); } } ``` 以上代码在32位JVM上和64位JVM上运行结果将不一致 从程序得到的结果来看,32位的HotSpot没有把long和double的读写实现为原子操作。 在读写的时候,分成两次操作,每次读写32位。因为采用了这种策略,所以64位的long和double的读与写都不是原子操作 ### 结论 * 对于64位的long和double,如果没有被volatile修饰,那么对其操作可以不是原子的。在操作的时候,可以分成两步,每次对32位操作; * 如果使用volatile修饰long和double,那么其读写都是原子操作; * 在实现JVM时,可以自由选择是否把读写long和double作为原子操作; * java中对于long和double类型的写操作不是原子操作,而是分成了两个32位的写操作。读操作是否也分成了两个32位的读呢?在JSR-133之前的规范中,读也是分成了两个32位的读,但是从JSR-133规范开始,即JDK5开始,读操作也都具有原子性; * java中对于其他类型的读写操作都是原子操作\(除long和double类型以外\); * 对于引用类型的读写操作都是原子操作,无论引用类型的实际类型是32位的值还是64位的值; * 对于long类型的不恰当操作可能读取到从未出现过的值。而对于int的不恰当操作则可能读取到旧的值;