Java指令重排序的問題解決
下面小編就為大家帶來一篇淺談java指令重排序的問題。小編覺得蠻不錯的,現在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
指令重排序是個比較複雜、覺得有些不可思議的問題,同樣是先以例子開頭(建議大家跑下例子,這是實實在在可以重現的,重排序的機率還是挺高的),有個感性的認識
/** * 一个简单的展示Happen-Before的例子. * 这里有两个共享变量:a和flag,初始值分别为0和false.在ThreadA中先给 a=1,然后flag=true. * 如果按照有序的话,那么在ThreadB中如果if(flag)成功的话,则应该a=1,而a=a*1之后a仍然为1,下方的if(a==0)应该永远不会为 * 真,永远不会打印. * 但实际情况是:在试验100次的情况下会出现0次或几次的打印结果,而试验1000次结果更明显,有十几次打印. */ public class SimpleHappenBefore { /** 这是一个验证结果的变量 */ private static int a=0; /** 这是一个标志位 */ private static boolean flag=false; public static void main(String[] args) throws InterruptedException { //由于多线程情况下未必会试出重排序的结论,所以多试一些次 for(int i=0;i<1000;i++){ ThreadA threadA=new ThreadA(); ThreadB threadB=new ThreadB(); threadA.start(); threadB.start(); //这里等待线程结束后,重置共享变量,以使验证结果的工作变得简单些. threadA.join(); threadB.join(); a=0; flag=false; } } static class ThreadA extends Thread{ public void run(){ a=1; flag=true; } } static class ThreadB extends Thread{ public void run(){ if(flag){ a=a*1; } if(a==0){ System.out.println("ha,a==0"); } } } }
例子比較簡單,也添加了註釋,不再詳細敘述。
什麼是指令重新排序?有兩個層面:
在虛擬機層面,為了盡可能減少記憶體操作速度遠慢於CPU運行速度所帶來的CPU空置的影響,虛擬機會按照自己的一些規則(這規則後面再敘述)將程式編寫順序打亂-即寫在後面的程式碼在時間順序上可能會先執行,而寫在前面的程式碼會後執行-以盡可能充分地利用CPU。拿上面的例子來說:假如不是a=1的操作,而是a=new byte[1024*1024](分配1M空間)`,那麼它會運行地很慢,此時CPU是等待其執行結束呢,還是先執行下面那句flag=true呢?顯然,先執行flag=true可以提前使用CPU,加快整體效率,當然這樣的前提是不會產生錯誤(什麼樣的錯誤後面再說)。雖然這裡有兩種情況:後面的程式碼先於前面的程式碼開始執行;前面的程式碼先開始執行,但當效率較慢的時候,後面的程式碼開始執行並先於前面的程式碼執行結束。不管誰先開始,總之後面的程式碼在某些情況下存在先結束的可能。
在硬體層面,CPU會將接收到的一批指令依照其規則重排序,同樣是基於CPU速度比快取速度快的原因,和上一點的目的類似,只是硬體處理的話,每次只能在接收到的有限指令範圍內重新排序,而虛擬機器可以在更大層面、更多指令範圍內重新排序。硬體的重排序機制參考《從JVM並發看CPU記憶體指令重排序(Memory Reordering)》
重排序很不好理解,上面只是簡單地提了下其場景,要想較好地理解這個概念,需要構造一些例子和圖表,在這裡介紹兩篇介紹比較詳細、生動的文章《happens-before俗解》和《深入理解Java內存模型(二)——重排序》。其中的「as-if-serial」是應該掌握的,也就是:不管怎麼重新排序,單執行緒程式的執行結果不能被改變。編譯器、執行時間和處理器都必須遵守「as-if-serial」語意。拿個簡單範例來說,
public void execute(){ int a=0; int b=1; int c=a+b; }
這裡a=0,b=1兩句可以隨便排序,不影響程式邏輯結果,但c=a+b這句必須在前兩句的後面執行。
從前面那個例子可以看到,重排序在多執行緒環境下出現的機率還是挺高的,在關鍵字上有volatile和synchronized可以停用重排序,除此之外還有一些規則,也正是這些規則,使得我們在平時的程式工作中沒有感受到重排序的壞處。
程式次序規則(Program Order Rule):在一個執行緒內,依照程式碼順序,書寫在前面的操作先行發生於書寫在後面的操作。準確地說應該是控制流順序而不是程式碼順序,因為要考慮分支、循環等結構。
監視器鎖定規則(Monitor Lock Rule):一個unlock操作先行發生於後面對同一個物件鎖的lock操作。這裡強調的是同一個鎖,而「後面」指的是時間上的先後順序,如發生在其他執行緒的lock操作。
volatile變數規則(Volatile Variable Rule):對一個volatile變數的寫入操作發生於後面對這個變數的讀取操作,這裡的「後面」也指的是時間上的先後順序。
執行緒啟動規則(Thread Start Rule):Thread獨享的start()方法先行於此執行緒的每一個動作。
執行緒終止規則(Thread Termination Rule):執行緒中的每個操作都先行發生於對此執行緒的終止偵測,我們可以透過Thread.join()方法結束、Thread.isAlive()的返回值偵測到執行緒已經終止執行。
執行緒中斷規則(Thread Interruption Rule):對執行緒interrupte()方法的呼叫優先於被中斷執行緒的程式碼偵測到中斷事件的發生,可以透過Thread.interrupted()方法偵測執行緒是否已中斷。
物件終結原則(Finalizer Rule):一個物件的初始化完成(建構子執行結束)先行發生於它的finalize()方法的開始。
傳遞性(Transitivity):如果操作A先行發生於操作B,操作B先行發生於操作C,那就可以得出操作A先行發生於操作C的結論。
正是以上這些規則保障了happen-before的順序,如果不符合以上規則,那麼在多執行緒環境下就不能保證執行順序等同於程式碼順序,也就是「如果在本執行緒中觀察,所有的操作都是有序的;如果在一個線程中觀察另外一個線程,則不符合以上規則的都是無序的”,因此,如果我們的多線程程序依賴於代碼書寫順序,那麼就要考慮是否符合以上規則,如果不符合就要透過一些機制使其符合,最常用的就是synchronized、Lock以及volatile修飾符。
以上是Java指令重排序的問題解決的詳細內容。更多資訊請關注PHP中文網其他相關文章!

熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

AI Hentai Generator
免費產生 AI 無盡。

熱門文章

熱工具

記事本++7.3.1
好用且免費的程式碼編輯器

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
強大的PHP整合開發環境

Dreamweaver CS6
視覺化網頁開發工具

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)

熱門話題

Java 8引入了Stream API,提供了一種強大且表達力豐富的處理數據集合的方式。然而,使用Stream時,一個常見問題是:如何從forEach操作中中斷或返回? 傳統循環允許提前中斷或返回,但Stream的forEach方法並不直接支持這種方式。本文將解釋原因,並探討在Stream處理系統中實現提前終止的替代方法。 延伸閱讀: Java Stream API改進 理解Stream forEach forEach方法是一個終端操作,它對Stream中的每個元素執行一個操作。它的設計意圖是處

膠囊是一種三維幾何圖形,由一個圓柱體和兩端各一個半球體組成。膠囊的體積可以通過將圓柱體的體積和兩端半球體的體積相加來計算。本教程將討論如何使用不同的方法在Java中計算給定膠囊的體積。 膠囊體積公式 膠囊體積的公式如下: 膠囊體積 = 圓柱體體積 兩個半球體體積 其中, r: 半球體的半徑。 h: 圓柱體的高度(不包括半球體)。 例子 1 輸入 半徑 = 5 單位 高度 = 10 單位 輸出 體積 = 1570.8 立方單位 解釋 使用公式計算體積: 體積 = π × r2 × h (4

Spring Boot簡化了可靠,可擴展和生產就緒的Java應用的創建,從而徹底改變了Java開發。 它的“慣例慣例”方法(春季生態系統固有的慣例),最小化手動設置
