java原子性操作
1. long和double類型操作的非原子性探究
深入Java虛擬機中提到,對於小於等於32位的基本類型如int,其操作是原子性的。然而,對於long和double類型,某些32位的Java虛擬機並未提供原子性操作,這可能導致錯誤數據的產生。相反,64位的Java虛擬機對long和double變數的處理是原子的。
在32位的Java虛擬機中,long和double變數被視為兩個32位的原子值,而非一個64位的原子值。這意味著在將long類型值保存至內存時,可能存在兩次32位的寫操作。若兩個競爭線程分別嘗試寫入不同的值至內存,則可能形成錯誤的內存值。當多個線程操作同一個變數時,流程大致分為幾個步驟。
例如,若執行順序為(1)(4)(3)(2),則內存中的值將由線程1的高32位和線程2的低32位組成,從而形成一個錯誤的組合值。
在實際測試中,發現程序在while循環時陷入死循環,這表明所使用的JVM為64位。在64位JVM中,double和long的賦值操作是原子的。然而,當使用32位JVM時,程序能跳出循環,表明賦值過程被拆分,無法保證原子性。
為了解決這一問題,可以查閱Oracle Java Spec文檔。文檔中提到,某些Java實現可能將64位long或double值的寫操作劃分為兩次相鄰的32位值寫操作,以追求更高的效率。然而,這一行為是實現可以自行決定的,Java虛擬機可以自行決定是否原子性地對待long和double值的寫操作。考慮到內存模型的目的,將非volatile的long或double值的單次寫操作視為兩次分開的寫操作(每次32位),可能會導致某個線程只讀取到一次寫操作中的前32位,而另一部分寫操作的後32位則未被讀取。對於volatile的long和double值的讀寫操作總是原子的,且對於引用的操作,不論其實現採用32位還是64位,總能保持原子性。
值得注意的是,volatile只能保證可見性,而非原子性。但是,通過將long和double變數聲明為volatile,可以確保其操作的原子性。因此,鼓勵虛擬機實現者避免將64位值的寫操作拆分為兩次,同時建議編碼人員將共享的64位值聲明為volatile,或者正確同步程序以避免可能的並發問題。