溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

java中怎么對(duì)指令進(jìn)行重新排序

發(fā)布時(shí)間:2020-12-08 15:21:15 來(lái)源:億速云 閱讀:127 作者:Leah 欄目:編程語(yǔ)言

本篇文章給大家分享的是有關(guān)java中怎么對(duì)指令進(jìn)行重新排序,小編覺(jué)得挺實(shí)用的,因此分享給大家學(xué)習(xí),希望大家閱讀完這篇文章后可以有所收獲,話不多說(shuō),跟著小編一起來(lái)看看吧。

指令重排序是個(gè)比較復(fù)雜、覺(jué)得有些不可思議的問(wèn)題,同樣是先以例子開(kāi)頭(建議大家跑下例子,這是實(shí)實(shí)在在可以重現(xiàn)的,重排序的概率還是挺高的),有個(gè)感性的認(rèn)識(shí)

/**
 * 一個(gè)簡(jiǎn)單的展示Happen-Before的例子.
 * 這里有兩個(gè)共享變量:a和flag,初始值分別為0和false.在ThreadA中先給  a=1,然后flag=true.
 * 如果按照有序的話,那么在ThreadB中如果if(flag)成功的話,則應(yīng)該a=1,而a=a*1之后a仍然為1,下方的if(a==0)應(yīng)該永遠(yuǎn)不會(huì)為
 * 真,永遠(yuǎn)不會(huì)打印.
 * 但實(shí)際情況是:在試驗(yàn)100次的情況下會(huì)出現(xiàn)0次或幾次的打印結(jié)果,而試驗(yàn)1000次結(jié)果更明顯,有十幾次打印.
 */
public class SimpleHappenBefore {
 /** 這是一個(gè)驗(yàn)證結(jié)果的變量 */
 private static int a=0;
 /** 這是一個(gè)標(biāo)志位 */
 private static boolean flag=false;
 public static void main(String[] args) throws InterruptedException {
  //由于多線程情況下未必會(huì)試出重排序的結(jié)論,所以多試一些次
  for(int i=0;i<1000;i++){
   ThreadA threadA=new ThreadA();
   ThreadB threadB=new ThreadB();
   threadA.start();
   threadB.start();
   //這里等待線程結(jié)束后,重置共享變量,以使驗(yàn)證結(jié)果的工作變得簡(jiǎn)單些.
   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");
   }
  }
 }
}

例子比較簡(jiǎn)單,也添加了注釋,不再詳細(xì)敘述。

什么是指令重排序?有兩個(gè)層面:

在虛擬機(jī)層面,為了盡可能減少內(nèi)存操作速度遠(yuǎn)慢于CPU運(yùn)行速度所帶來(lái)的CPU空置的影響,虛擬機(jī)會(huì)按照自己的一些規(guī)則(這規(guī)則后面再敘述)將程序編寫(xiě)順序打亂——即寫(xiě)在后面的代碼在時(shí)間順序上可能會(huì)先執(zhí)行,而寫(xiě)在前面的代碼會(huì)后執(zhí)行——以盡可能充分地利用CPU。拿上面的例子來(lái)說(shuō):假如不是a=1的操作,而是a=new byte[1024*1024](分配1M空間)`,那么它會(huì)運(yùn)行地很慢,此時(shí)CPU是等待其執(zhí)行結(jié)束呢,還是先執(zhí)行下面那句flag=true呢?顯然,先執(zhí)行flag=true可以提前使用CPU,加快整體效率,當(dāng)然這樣的前提是不會(huì)產(chǎn)生錯(cuò)誤(什么樣的錯(cuò)誤后面再說(shuō))。雖然這里有兩種情況:后面的代碼先于前面的代碼開(kāi)始執(zhí)行;前面的代碼先開(kāi)始執(zhí)行,但當(dāng)效率較慢的時(shí)候,后面的代碼開(kāi)始執(zhí)行并先于前面的代碼執(zhí)行結(jié)束。不管誰(shuí)先開(kāi)始,總之后面的代碼在一些情況下存在先結(jié)束的可能。

在硬件層面,CPU會(huì)將接收到的一批指令按照其規(guī)則重排序,同樣是基于CPU速度比緩存速度快的原因,和上一點(diǎn)的目的類似,只是硬件處理的話,每次只能在接收到的有限指令范圍內(nèi)重排序,而虛擬機(jī)可以在更大層面、更多指令范圍內(nèi)重排序。硬件的重排序機(jī)制參見(jiàn)《從JVM并發(fā)看CPU內(nèi)存指令重排序(Memory Reordering)》

重排序很不好理解,上面只是簡(jiǎn)單地提了下其場(chǎng)景,要想較好地理解這個(gè)概念,需要構(gòu)造一些例子和圖表,在這里介紹兩篇介紹比較詳細(xì)、生動(dòng)的文章《happens-before俗解》和《深入理解Java內(nèi)存模型(二)——重排序》。其中的“as-if-serial”是應(yīng)該掌握的,即:不管怎么重排序,單線程程序的執(zhí)行結(jié)果不能被改變。編譯器、運(yùn)行時(shí)和處理器都必須遵守“as-if-serial”語(yǔ)義。拿個(gè)簡(jiǎn)單例子來(lái)說(shuō),

public void execute(){
 int a=0;
 int b=1;
 int c=a+b;
}

這里a=0,b=1兩句可以隨便排序,不影響程序邏輯結(jié)果,但c=a+b這句必須在前兩句的后面執(zhí)行。

從前面那個(gè)例子可以看到,重排序在多線程環(huán)境下出現(xiàn)的概率還是挺高的,在關(guān)鍵字上有volatile和synchronized可以禁用重排序,除此之外還有一些規(guī)則,也正是這些規(guī)則,使得我們?cè)谄綍r(shí)的編程工作中沒(méi)有感受到重排序的壞處。

程序次序規(guī)則(Program Order Rule):在一個(gè)線程內(nèi),按照代碼順序,書(shū)寫(xiě)在前面的操作先行發(fā)生于書(shū)寫(xiě)在后面的操作。準(zhǔn)確地說(shuō)應(yīng)該是控制流順序而不是代碼順序,因?yàn)橐紤]分支、循環(huán)等結(jié)構(gòu)。

監(jiān)視器鎖定規(guī)則(Monitor Lock Rule):一個(gè)unlock操作先行發(fā)生于后面對(duì)同一個(gè)對(duì)象鎖的lock操作。這里強(qiáng)調(diào)的是同一個(gè)鎖,而“后面”指的是時(shí)間上的先后順序,如發(fā)生在其他線程中的lock操作。

volatile變量規(guī)則(Volatile Variable Rule):對(duì)一個(gè)volatile變量的寫(xiě)操作發(fā)生于后面對(duì)這個(gè)變量的讀操作,這里的“后面”也指的是時(shí)間上的先后順序。

線程啟動(dòng)規(guī)則(Thread Start Rule):Thread獨(dú)享的start()方法先行于此線程的每一個(gè)動(dòng)作。

線程終止規(guī)則(Thread Termination Rule):線程中的每個(gè)操作都先行發(fā)生于對(duì)此線程的終止檢測(cè),我們可以通過(guò)Thread.join()方法結(jié)束、Thread.isAlive()的返回值檢測(cè)到線程已經(jīng)終止執(zhí)行。

線程中斷規(guī)則(Thread Interruption Rule):對(duì)線程interrupte()方法的調(diào)用優(yōu)先于被中斷線程的代碼檢測(cè)到中斷事件的發(fā)生,可以通過(guò)Thread.interrupted()方法檢測(cè)線程是否已中斷。

對(duì)象終結(jié)原則(Finalizer Rule):一個(gè)對(duì)象的初始化完成(構(gòu)造函數(shù)執(zhí)行結(jié)束)先行發(fā)生于它的finalize()方法的開(kāi)始。

傳遞性(Transitivity):如果操作A先行發(fā)生于操作B,操作B先行發(fā)生于操作C,那就可以得出操作A先行發(fā)生于操作C的結(jié)論。

正是以上這些規(guī)則保障了happen-before的順序,如果不符合以上規(guī)則,那么在多線程環(huán)境下就不能保證執(zhí)行順序等同于代碼順序,也就是“如果在本線程中觀察,所有的操作都是有序的;如果在一個(gè)線程中觀察另外一個(gè)線程,則不符合以上規(guī)則的都是無(wú)序的”,因此,如果我們的多線程程序依賴于代碼書(shū)寫(xiě)順序,那么就要考慮是否符合以上規(guī)則,如果不符合就要通過(guò)一些機(jī)制使其符合,最常用的就是synchronized、Lock以及volatile修飾符。

以上就是java中怎么對(duì)指令進(jìn)行重新排序,小編相信有部分知識(shí)點(diǎn)可能是我們?nèi)粘9ぷ鲿?huì)見(jiàn)到或用到的。希望你能通過(guò)這篇文章學(xué)到更多知識(shí)。更多詳情敬請(qǐng)關(guān)注億速云行業(yè)資訊頻道。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI