天天看點

JVM學習之路(六)——指令重排序

JVM學習之路(一)——java程式執行流程

JVM學習之路(二)——JVM的内部結構

JVM學習之路(三)——JVM内部結構詳細介紹及其互相作用

JVM學習之路(四)——記憶體模型(java多線程通信)

JVM學習之路(五)——如何保證可見性

六、指令重排序

根據以往學習多線程的經驗,往往就會碰到這樣一些例子:明明代碼是按照想要的邏輯寫的,但是一旦程式執行完後,就出現了一些意想不到的情況,那時隻知道是多線程運作的時候出現了問題,具體怎麼回事也沒搞清楚過。在學習JVM的時候才發現導緻這種問題的原因——指令重排序。

一、指令重排序案例重制:

有這樣一段程式:

int a = 0;
boolean flag = false;

//線程1

public void writer() {

a = 1;

flag = true;

}

//線程2

public void reader() {

if (flag) {

int i= a+1;

...... }

}
           

線程1依次執行a=1,flag=true;線程2判斷到flag==true後,設定i=a+1。根據代碼語義,我們可能會推斷此時i的值等于2,因為線程2在判斷flag==true時,線程1已經執行了a=1;是以i的值等于a+1=1+1=2;但真實情況卻不一定如此,引起這個問題的原因是線程1内部的兩條語句a=1;flag=true;可能被重新排序執行,如圖:

JVM學習之路(六)——指令重排序

這就是“指令重排序”問題的案例重制。兩個指派語句盡管他們的代碼順序是一前一後的,但真正執行時卻不一定按照代碼順序執行。

那我們就有疑問了,這個指令重排序不會讓我們的程式亂套嗎?我寫的程式都不按我的代碼流程走,這還要得?Java、CPU、記憶體之間的那一套嚴格的指令重排序規則讓我們大可放心,這套規則規定了哪些可以重排,哪些不可以重排。這樣,我們的程式就不會亂套了。

二、java程式從編譯到執行會經曆哪些重排序

1、編譯器優化重排序:屬于java編譯器重排序,會按照JMM(Java記憶體模型)的規範嚴格進行,編譯器重排序一般不會對程式的正确邏輯造成影響;

2、指令級并行重排序:屬于CPU重排序,CPU的事情JMM就不好管了,怎麼辦呢?JMM會要求java編譯器在生成指令時加入記憶體屏障。可以将記憶體屏障了解為一個密不透風的保護罩,把不能重排序的java指令保護起來,那麼處理器在遇到記憶體屏障保護的指令時就不會對它進行重排序了;

3、記憶體系統重排序:屬于CPU重排序,情況與2相似。

經過這3個步驟的重排序,一開始編寫的.java源代碼才有了一個最終可執行的指令序列。

三、單線程中,不會被重排序的邏輯:

JVM學習之路(六)——指令重排序

由于以上3種情況,任意改變任何一個代碼的順序,結果都會大不相同。是以,對于單線程中這樣的邏輯代碼,是不會被重排序的。

繼續閱讀