JVM中對long的操做是否是原子操做?java
爲何對long的操做不是原子的?less
在硬件,操做系統,JVM都是64位的狀況下呢?jvm
java中基本類型中,long和double的長度都是8個字節,32位(4字節)處理器對其讀寫操做沒法一次完成,那麼,JVM,long和double是原子性的嗎?ide
首先,經過一段程序對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的值打印出來ui
若是對long的寫入和讀取操做是原子性的,那麼,field的值只多是1或者-1this
運行結果以下atom
32-bit
出現錯誤結果-4294967295
運行正確
能夠看出,當線程t1,t2同時對long進行寫的時候,long出現了既不是t1寫入的值,又不是t2寫入的值。能夠推測,jvm中對long的操做並不是原子操做。spa
JVM內存模型中定義了8中原子操做:操作系統
lock:將一個變量標識爲被一個線程獨佔狀態
unclock:將一個變量從獨佔狀態釋放出來,釋放後的變量才能夠被其餘線程鎖定
read:將一個變量的值從主內存傳輸到工做內存中,以便隨後的load操做
load:把read操做從主內存中獲得的變量值放入工做內存的變量的副本中
use:把工做內存中的一個變量的值傳給執行引擎,每當虛擬機遇到一個使用到變量的指令時都會使用該指令
assign:把一個從執行引擎接收到的值賦給工做內存中的變量,每當虛擬機遇到一個給變量賦值的指令時,都要使用該操做
store:把工做內存中的一個變量的值傳遞給主內存,以便隨後的write操做
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.
從規定中咱們能夠知道
對於64位的long和double,若是沒有被volatile修飾,那麼對其操做能夠不是原子的。在操做的時候,能夠分紅兩步,每次對32位操做。
若是使用volatile修飾long和double,那麼其讀寫都是原子操做
對於64位的引用地址的讀寫,都是原子操做
在實現JVM時,能夠自由選擇是否把讀寫long和double做爲原子操做
推薦JVM實現爲原子操做
從程序獲得的結果來看,32位的HotSpot沒有把long和double的讀寫實現爲原子操做。 在讀寫的時候,分紅兩次操做,每次讀寫32位。由於採用了這種策略,因此64位的long和double的讀與寫都不是原子操做。
對於64bit的環境來講,單次操做能夠操做64bit的數據,便可以以一次性讀寫long或double的整個64bit。所以咱們能夠猜想,在64位的環境下,long和double的讀寫有多是原子操做。 在換了64位的JVM以後,屢次運行,結果都是正確的
64-bit
運行正確
運行正確
結果代表,在64bit的虛擬機下,long的處理是原子性的。
https://mp.weixin.qq.com/s?__biz=MzUzMTA2NTU2Ng==&mid=2247486080&idx=2&sn=1fc60b6bf77308e17ab8cb731f33f99f&chksm=fa497531cd3efc27fd2479e4ffd6ba32d5900eae0c3aa565662a0d9a38667b403040aec811fc&mpshare=1&scene=1&srcid=0112sC5LAE1CqRDEp8sAolHv&key=0061820b7e211c2777061392c3d0fd794715b1defc7150ddc2f963aa9df561ca968ea5aa8ab89c6b1beaf5d33915386824e2aa2391c8178e005ee9d18c0fbf62b884a86c53bf1bd181d928af7f14c8f9&ascene=0&uin=MTA2NzUxMDAyNQ%3D%3D&devicetype=iMac+MacBookAir6%2C2+OSX+OSX+10.10.5+build(14F2511)&version=11020012&lang=zh_CN&pass_ticket=sv4l%2BGEe3jDMSGyGy8HbXOjLuiqF00ftuchHsiP4ANQLuOaLo%2FLBiGBEJRndwZZ5
模擬模型:
線程1 線程2
寫前32
讀前32+後32(這個地方出現讀取異常)
寫後32