Java 中 long 是不是原子操作?
點擊關(guān)注公眾號,Java干貨及時送達
真香!24W字的Java面試手冊(點擊查看)
Java中l(wèi)ong和double的原子性
“java中基本類型中,long和double的長度都是8個字節(jié),32位(4字節(jié))處理器對其讀寫操作無法一次完成,那么,JVM,long和double是原子性的嗎?
”
JVM中對long的操作是不是原子操作?
首先,通過一段程序?qū)ong的原子性進行判斷。測試程序如下:
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("出現(xiàn)錯誤結(jié)果" + 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類型的靜態(tài)變量field賦值為1,-1;t1,t2每次賦值后,會讀取field的值,若field值既不是1又不是-1,就將field的值打印出來
如果對long的寫入和讀取操作是原子性的,那么,field的值只可能是1或者-1
運行結(jié)果如下
32-bit
出現(xiàn)錯誤結(jié)果-4294967295
運行正確
可以看出,當線程t1,t2同時對long進行寫的時候,long出現(xiàn)了既不是t1寫入的值,又不是t2寫入的值??梢酝茰y,jvm中對long的操作并非原子操作。
為什么對long的操作不是原子的?
JVM內(nèi)存模型中定義了8種原子操作:
1、 lock:將一個變量標識為被一個線程獨占狀態(tài)
2、 unclock:將一個變量從獨占狀態(tài)釋放出來,釋放后的變量才可以被其他線程鎖定
read: 將一個變量的值從主內(nèi)存?zhèn)鬏數(shù)焦ぷ鲀?nèi)存中,以便隨后的load操作
load: 把read操作從主內(nèi)存中得到的變量值放入工作內(nèi)存的變量的副本中
use: 把工作內(nèi)存中的一個變量的值傳給執(zhí)行引擎,每當虛擬機遇到一個使用到變量的指令時都會使用該指令
assign: 把一個從執(zhí)行引擎接收到的值賦給工作內(nèi)存中的變量,每當虛擬機遇到一個給變量賦值的指令時,都要使用該操作
store: 把工作內(nèi)存中的一個變量的值傳遞給主內(nèi)存,以便隨后的write操作
write: 把store操作從工作內(nèi)存中得到的變量的值寫到主內(nèi)存中的變量
其中,與賦值,取值相關(guān)的包括 read,load,use,assign,store,write
按照這個規(guī)定,long的讀寫都是原子操作,與我們的實踐結(jié)果相反,為什會導致這種問題呢?
對于32位操作系統(tǒng)來說,單次次操作能處理的最長長度為32bit,而long類型8字節(jié)64bit,所以對long的讀寫都要兩條指令才能完成(即每次讀寫64bit中的32bit)。如果JVM要保證long和double讀寫的原子性,勢必要做額外的處理。那么,JVM有對這一情況進行額外處理嗎?針對這一問題可以參考Java語言規(guī)范文檔:jls-17 Non-Atomic Treatment of double and long(http://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.
”
“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.
”
從規(guī)定中我們可以知道
對于64位的long和double,如果沒有被volatile修飾,那么對其操作可以不是原子的。在操作的時候,可以分成兩步,每次對32位操作。 如果使用volatile修飾long和double,那么其讀寫都是原子操作 對于64位的引用地址的讀寫,都是原子操作 在實現(xiàn)JVM時,可以自由選擇是否把讀寫long和double作為原子操作 推薦JVM實現(xiàn)為原子操作
從程序得到的結(jié)果來看,32位的HotSpot沒有把long和double的讀寫實現(xiàn)為原子操作。在讀寫的時候,分成兩次操作,每次讀寫32位。因為采用了這種策略,所以64位的long和double的讀與寫都不是原子操作。
在硬件,操作系統(tǒng),JVM都是64位的情況下呢?
對于64bit的環(huán)境來說,單次操作可以操作64bit的數(shù)據(jù),即可以以一次性讀寫long或double的整個64bit。因此我們可以猜測,在64位的環(huán)境下,long和double的讀寫有可能是原子操作。在換了64位的JVM之后,多次運行,結(jié)果都是正確的
64-bit
運行正確
運行正確
結(jié)果表明,在64bit的虛擬機下,long的處理是原子性的。
如有文章對你有幫助,
歡迎關(guān)注??、點贊??、轉(zhuǎn)發(fā)??!
推薦, Java面試手冊 內(nèi)容包括網(wǎng)絡(luò)協(xié)議、Java基礎(chǔ)、進階、字符串、集合、并發(fā)、JVM、數(shù)據(jù)結(jié)構(gòu)、算法、MySQL、Redis、Mongo、Spring、SpringBoot、MyBatis、SpringCloud、Linux以及各種中間件(Dubbo、Nginx、Zookeeper、MQ、Kafka、ElasticSearch)等等... 點擊文末“閱讀原文”可直達

