您好,登錄后才能下訂單哦!
這篇文章給大家分享的是有關java指令重排現(xiàn)象兩個階段的示例分析的內(nèi)容。小編覺得挺實用的,因此分享給大家做個參考,一起跟隨小編過來看看吧。
解釋型語言是在運行期間執(zhí)行編譯+運行動作,所以運行效率較編譯型語言低。Java既可以作為解釋型語言去用,也可以作為編譯型語言。但是主流的做法是當成編譯型語言在用。那Java在編譯期做了指令重排優(yōu)化嗎?做了哪些優(yōu)化?能不能讓我看看?為了滿足大家的好奇,安排。
這里先解釋下編譯期:像c/c++只有一個編譯期,就是調(diào)用gcc命令將c/c++代碼編譯成匯編代碼。
但是Java中有兩個編譯期:
1、調(diào)用javac命令將Java代碼編譯成Java字節(jié)碼;
2、Unix派系平臺上調(diào)用gcc命令將openjdk源碼編譯成匯編代碼。
網(wǎng)上所有的文章都是在講第一種,而且都是講概念,以訛傳訛。我這篇文章不僅兩種都講,還都用代碼+圖片的方式證明給你看。所以想學底層,不找一個靠譜的師傅是學不會學不明白的,因為第一你不知道這個知識點牽扯得有多深,第二兩個觀點擺在你面前,你不知道哪個對那個錯。
這里我先把結(jié)論給大家吧:編譯期間,Java中所謂的指令重排主要是說編譯openjdk時的指令重排,將Java代碼編譯成Java字節(jié)碼是沒有做指令重排的。即你加不加volatile,生成的字節(jié)碼文件是一樣的。是不是顛覆了你對這塊的認知呢!不信?看案例。
可能有人要問了,如果加不加volatile生成的字節(jié)碼文件都一個樣,那在運行的時候JVM是怎么知道的呢?類屬性在JVM中存儲的時候會有一個屬性:Access flags。JVM在運行的時候就是通過該屬性來判斷操作的類屬性有沒有加volatile修飾,上圖。
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; } }
稍微解釋下這段代碼:有兩個線程:我線程、基友線程。『我線程』通過死循環(huán)阻塞在那里等待『基友線程』找到筆送過來,然后開始寫字?!夯丫€程』等待一會就去找筆,找到了就送過去。
可以發(fā)現(xiàn)加不加volatile,生成的字節(jié)碼是一樣的。
指令重排是編譯器優(yōu)化中的一種,編譯openjdk是啟用了O2級編譯器優(yōu)化,如圖。
O2級優(yōu)化做了哪些優(yōu)化?比如優(yōu)化無效代碼、編譯期完成簡單運算、處理編譯期屏障……那gcc有多少級優(yōu)化?有興趣的童鞋可以自行學習,百度搜索關鍵詞:-O2。
優(yōu)化無效代碼,看圖(我就不貼C++代碼了)
不知道大家有沒有聽過一個詞:CPU亂序執(zhí)行。亂序執(zhí)行是相對于順序執(zhí)行來說的。計算機剛被發(fā)明的時候都是順序執(zhí)行,后來為了提升CPU運行效率,升級成了亂序執(zhí)行。
那為什么亂序執(zhí)行就提高了運行效率呢?有興趣的童鞋可以去研究下,關鍵詞:指令流水線。
所以計算機這行,如果你覺得大學學的那些基礎知識不重要,你看我的文章就明白有多重要。這行走到最后較量的就是這些東西,就是看誰研究得更深入更底層更明了。
因為現(xiàn)在的CPU都是采用亂序執(zhí)行,這樣在運行程序的過程中就帶來了指令重排的現(xiàn)象。這是在運行期,在CPU內(nèi)部發(fā)生的,我就沒辦法證明給你看了。但就算是亂序執(zhí)行提高了效率,那也不能改變我程序的意愿,這就引出了一個概念:as-if-serial。
何謂as-if-serial呢?簡單的說就是不管你在編譯期或者在運行期怎么做指令重排,單線程環(huán)境下程序的執(zhí)行結(jié)果不能改變。說白了這是指令重排的底線,是必須遵守的規(guī)范。那如何保證呢?這就引出了另外兩個難以理解的知識點:happens-before、內(nèi)存屏障。
happens-before是做什么的呢?簡單的說就是告訴寫JVM的人,你寫JVM的時候要遵循這幾條規(guī)則,這幾條規(guī)則是你JVM默認要做到的,而不用程序猿在寫代碼的時候需要去想去做控制。比如對象的初始化動作一定要先于finalize方法執(zhí)行前完成。其他幾個規(guī)則我就不細說了,都很好理解,童鞋們自行去學習下。
有些流程的順序是可以提前知曉并確定下來,但有些流程的順序是無法提前知曉的,比如你公司的業(yè)務,寫JVM的人肯定不知道,所以依然需要程序猿根據(jù)業(yè)務需要來控制,那從JVM層面來說,我給你提供機制。內(nèi)存屏障就是這種機制中的一種,其他的還有各種鎖。
感謝各位的閱讀!關于“java指令重排現(xiàn)象兩個階段的示例分析”這篇文章就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,讓大家可以學到更多知識,如果覺得文章不錯,可以把它分享出去讓更多的人看到吧!
免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。