Java記憶體區域與Java記憶體模型
阿新 • • 發佈:2018-12-31
- 原子性:原子性指的是一個操作是不可中斷的,即使是在多執行緒環境下,一個操作一旦開始就不會被其他執行緒影響。比如對於一個靜態變數int x,兩條執行緒同時對他賦值,執行緒A賦值為1,而執行緒B賦值為2,不管執行緒如何執行,最終x的值要麼是1,要麼是2,執行緒A和執行緒B間的操作是沒有干擾的,這就是原子性操作,不可被中斷的特點。有點要注意的是,對於32位系統的來說,long型別資料和double型別資料(對於基本資料型別,byte,short,int,float,boolean,char讀寫是原子操作),它們的讀寫並非原子性的,也就是說如果存在兩條執行緒同時對long型別或者double型別的資料進行讀寫是存在相互干擾的,因為對於32位虛擬機器來說,每次原子讀寫是32位的,而long和double則是64位的儲存單元,這樣會導致一個執行緒在寫時,操作完前32位的原子操作後,輪到B執行緒讀取時,恰好只讀取到了後32位的資料,這樣可能會讀取到一個既非原值又不是執行緒修改值的變數,它可能是“半個變數”的數值,即64位資料被兩個執行緒分成了兩次讀取。但也不必太擔心,因為讀取到“半個變數”的情況比較少見,至少在目前的商用的虛擬機器中,幾乎都把64位的資料的讀寫操作作為原子操作來執行,因此對於這個問題不必太在意,知道這麼回事即可。
- 理解指令重排
- 計算機在執行程式時,為了提高效能,編譯器和處理器的常常會對指令做重排,一般分以下3種:
- 編譯器優化的重排:編譯器在不改變單執行緒程式語義的前提下,可以重新安排語句的執行順序。
- 指令並行的重排:現代處理器採用了指令級並行技術來將多條指令重疊執行。如果不存在資料依賴性(即後一個執行的語句無需依賴前面執行的語句的結果),處理器可以改變語句對應的機器指令的執行順序。
- 記憶體系統的重排:由於處理器使用快取和讀寫快取衝區,這使得載入(load)和儲存(store)操作看上去可能是在亂序執行,因為三級快取的存在,導致記憶體與快取的資料同步存在時間差。
- 其中編譯器優化的重排屬於編譯期重排,指令並行的重排和記憶體系統的重排屬於處理器重排,在多執行緒環境中,這些重排優化可能會導致程式出現記憶體可見性問題。
- 計算機在執行程式時,為了提高效能,編譯器和處理器的常常會對指令做重排,一般分以下3種:
- 理解指令重排
- 可見性:理解了指令重排現象後,可見性容易了,可見性指的是當一個執行緒修改了某個共享變數的值,其他執行緒是否能夠馬上得知這個修改的值。對於序列程式來說,可見性是不存在的,因為我們在任何一個操作中修改了某個變數的值,後續的操作中都能讀取這個變數值,並且是修改過的新值。但在多執行緒環境中可就不一定了,前面我們分析過,由於執行緒對共享變數的操作都是執行緒拷貝到各自的工作記憶體進行操作後才寫回到主記憶體中的,這就可能存在一個執行緒A修改了共享變數x的值,還未寫回主記憶體時,另外一個執行緒B又對主記憶體中同一個共享變數x進行操作,但此時A執行緒工作記憶體中共享變數x對執行緒B來說並不可見,這種工作記憶體與主記憶體同步延遲現象就造成了可見性問題,另外指令重排以及編譯器優化也可能導致可見性問題,通過前面的分析,我們知道無論是編譯器優化還是處理器優化的重排現象,在多執行緒環境下,確實會導致程式輪序執行的問題,從而也就導致可見性問題。
- 有序性:有序性是指對於單執行緒的執行程式碼,我們總是認為程式碼的執行是按順序依次執行的,這樣的理解並沒有毛病,畢竟對於單執行緒而言確實如此,但對於多執行緒環境,則可能出現亂序現象,因為程式編譯成機器碼指令後可能會出現指令重排現象,重排後的指令與原指令的順序未必一致,要明白的是,在Java程式中,倘若在本執行緒內,所有操作都視為有序行為,如果是多執行緒環境下,一個執行緒中觀察另外一個執行緒,所有操作都是無序的,前半句指的是單執行緒內保證序列語義執行的一致性,後半句則指指令重排現象和工作記憶體與主記憶體同步延遲現象。