1. 程式人生 > 程式設計 >關於指令重排現象的兩個階段詳解

關於指令重排現象的兩個階段詳解

目錄
  • 編譯期指令重排
    • 1、上神祕程式碼
    • 2、編譯成位元組碼(沒加volatile)
    • 3、編譯成Java位元組碼(加了volatile)
    • 4、編譯器優化
  • 執行期指令重排

    那什麼時候會產生指令重排現象呢?兩個階段:1、編譯期;2KvMcg、執行期。

    編譯期指令重排

    解釋型語言是在執行期間執行編譯+執行動作,所以執行效率較編譯型語言低。Java既可以作為解釋型語言去用,也可以作為編譯型語言。但是主流的做法是當成編譯型語言在用。那Java在編KvMcg譯期做了指令重排優化嗎?做了哪些優化?能不能讓我看看?為了滿足大家的好奇,安排。

    這裡先解釋下編譯期:像c/c++只有一個編譯期,就是呼叫gcc命令將c/c++程式碼編譯成彙編程式碼。

    但是Java中有兩個編譯期:

    • 1、呼叫javac命令將Java程式碼編譯成Java位元組碼;
    • 2、Unix派系平臺上呼叫gcc命令將openjdk原始碼編譯成彙編程式碼。

    網上所有的文章都是在講第一種,而且都是講概念,以訛傳訛。我這篇文章不僅兩種都講,還都用程式碼+圖片的方式證明給你看。所以想學底層,不找一個靠譜的師傅是學不會學不明白的,因為第一你不知道這個知識點牽扯得有多深,第二兩個觀點擺在你面前,你不知道哪個對那個錯。

    這裡我先把結論給大家吧:編譯期間,Java中所謂的指令重排主要是說編譯openjdk時的指令重排,將Java程式碼編譯成Java位元組碼是沒有做指令重排的。即你加不加volatile,生成的位元組碼檔案是一樣的。是不是顛覆了你對這塊的認知呢!不信?看案例。

    可能有人要問了,如果加不加volatile生成的位元組碼檔案都一個樣,那在執行的時候JVM是怎麼知道的呢?類屬性在JVM中儲存的時候會有一個屬性:Access flags。JVM在執行的時候就是通過該屬性來判斷操作的類屬性有沒有加volatile修飾,上圖。

    關於指令重排現象的兩個階段詳解

    關於指令重排現象的兩個階段詳解

    1、上神祕程式碼

    public class Test3 {
     public static /* volatile */ int found = 0;
     public static void main(String[] args) {
        new Thread(new Runnable() {
           public void run() {
              System.out.println("等基友送筆來...");
              while (0 == found) {
              }
              System.out.println("筆來了,開始寫字...");
           }
        },"我執行緒").start();
    
        new Thread(new Runnable() {
           public void run() {
              try {
           
    Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("基友找到筆了,送過去..."); change(); } },"基友執行緒").start(); } public static void change() { found = 1; } }

    稍微解釋下這段程式碼:有兩個執行緒:我執行緒、基友執行緒。『我執行緒』通過死迴圈阻塞在那裡等待『基友執行緒』找到筆送過來,然後開始寫字。『基友執行緒』等待一會就去找筆,找到了就送過去。

    2、編譯成Java位元組碼(沒加volatile)

    關於指令重排現象的兩個階段詳解

    3、編譯成Java位元組碼(加了volatile)

    關於指令重排現象的兩個階段詳解

    可以發現加不加volatile,生成的位元組碼是一樣的。

    4、編譯器優化

    指令重排是編譯器優化中的一種,編譯openjdk是啟用了O2級編譯器優化,如圖。

    關於指令重排現象的兩個階段詳解

    O2級優化做了哪些優化?比如優化無效程式碼、編譯期完成簡單運算、處理編譯期屏障……那gcc有多少級優化?有興趣的童鞋可以自行學習,百度搜索關鍵詞:-O2。

    優化無效程式碼,看圖(我就不貼C++程式碼了)

    關於指令重排現象的兩個階段詳解

    執行期指令重排

    不知道大家有沒有聽過一個詞:CPU亂序執行。亂序執行是相對於順序執行來說的。計算機剛被髮明的時候都是順序執行,後來為了提升CPU執行效率,升級成了亂序執行。

    那為什麼亂序執行就提高了執行效率呢?有興趣的童鞋可以去研究下,關鍵詞:指www.cppcns.com令流水線。

    所以計算機這行,如果你覺得大學學的那些基礎知識不重要,你看我的文章就明白有多重要。這行走到最後較量的就是這些東西,就是看誰研究得更深入更底層更明瞭。

    因為現在的CPU都是採用亂序執行,這樣在執行程式的過程中就帶來了指令重排的現象。這是在執行期,在CPU內部發生的,我就沒辦法證明給你看了。但就算是亂序執行提高了效率,那也不能改變我程式的意願,這就引出了一個概念:as-if-serial。

    何謂as-if-serial呢?簡單的說就是不管你在編譯期或者在執行期怎麼做指令重排,單執行緒環境下程式的執行結果不能改變。說白了這是指令重排的底線,是必須遵守的規範。那如何保證呢?這就引出了另外兩個難以理解的知識點:happens-before、記憶體屏障。

    happens-b客棧efore是做什麼的呢?簡單的說就是告訴寫JVM的人,你寫JVM的時候要遵循這幾條規則,這幾條規則是你JVM預設要做到的,而不用程式猿在寫程式碼的時候需要去想去做控制。比如物件的初始化動作一定要先於finalize方法執行前完成。其他幾個規則我就不細說了,都很好理解,童鞋們自行去學習下。

    有些流程的順序是可以提前知曉並確定下來,但有些流程的順序是無法提前知曉的,比如你公司的業務,寫JVM的人肯定不知道,所以依然需要程式猿根據業務需要來控制,那從JVM層面來說,我給你提供機制。記憶體屏障就是這種機制中的一種,其他的還有各種鎖。關於記憶體屏障,我之前已經寫了一篇文章深入講解了這塊,有興趣的同學可以去看看,傳送門記憶體屏障由來及實現思路

    以上就是關於指令重排現象的兩個階段詳解的詳細內容,更多關於指令重排現象的兩個階段的資料請關注我們其它相關文章!