在學習JAVA線程時候的遇到一個很奇怪的現象。讓我們先來看代碼
public class TestThread {
public static void main(String[] args) {
IRun ir = new IRun();
Thread it = new Thread(ir);
it.start();
try {
Thread.sleep(1000);
} catch (InterruptedException ex) {
Logger.getLogger(TestThread.class.getName()).log(Level.SEVERE, null, ex);
}
ir.setStop();
}
static class IRun implements Runnable {
boolean exec = true;
public void setStop() {
exec = false;
System.out.println("exec = " + exec);
}
@Override
public void run() {
int c = 0;
while (exec) {
c++;
}
System.out.println("退出了循環");
}
}
}
在上述程序首先創建一個Runnable對象ir,然後創建一個線程對象it,通過it.start()啟動線程,執行IRun類中的run()函數,執行一個while循環。while循環的條件由布爾變量exec控制。主程序中等待1秒鐘後,修改exec的值為false,按照正常的邏輯應該是在exec值為false後,循環結束執行下面輸出語句,然後線程結束,程序退出。
但是,上述代碼在運行後,將不會執行run()函數中的System.out.println("退出了循環");語句,程序一直保持運行。對這種現象,在帖子”關於JAVA線程,請大神幫忙“中still_rain給出的這種現象的原因是線程訪問的同步問題導致,當主線程(main函數所在的線程)中修改變量exec的值後,在創建執行的線程中訪問的exec的值沒有發生變化(或者說可能是主線程、執行線程這兩個線程exec變量是不同的對象,好像是說java會將一些資源在每個線程中復制一份?有待確認)。在exec變量前加上volatile修飾符後,程序運行正常,問題似乎解決。
現在以上述代碼為基礎,進行一次小小的修改。在run()函數中的while循環中加上一個類對象操作,比如新建一個字符串,則run()函數為
public void run() {
int c = 0;
while (exec) {
String s = new String("");
c++;
}
System.out.println("退出了循環");
}
}
運行程序,程序按預期的順序執行,順利結束。這時候不管exec變量是否有volatile修飾符,且只在while循環中存在類對象操作(新建對象、對象函數調用(調用的函數不能返回基本數據類型),以及像System.out.println這樣的操作),程序的運行都很正常,對這種現象就不是上述的同步問題能夠解釋的了。在帖子”關於JAVA線程,請大神幫忙“中still_rain(感謝熱情的回復)從編譯器優化的角度進行了解釋。當while循環中只有簡單的基本數據類型參與運算的時候由於執行速度太快,將while循環編譯成了while(true)語句。導致循環不會退出,從而循環後的輸出不會執行,且程序不會退出。
為了探究這個奇怪現象的原因,讓我們看看編譯後的字節碼,下圖是用jclasslib查看的字節碼,是修改前的run()函數字節碼。
從圖中可以看到,循環體從aload_0開始,到goto 2結束。控制跳轉的語句是ifeq 15,意思是如果值為0則跳轉到15,否則執行下面的語句。
當將上面的while(exec)語句修改為while(true)後,字節碼為:
由兩圖可以看出,編譯器並沒有將while(exec)語句優化為while(true)。再看修改後的代碼編譯的字節碼:
循環體從aload_0起,到goto 2結束。從ifeq 25下面的一行到astotre_2行,為String s = new String("");語句的字節碼,將這段去掉則字節碼與修改前的一樣。所以基本可以排除編譯器優化導致的程序不正常運行。
請看到這篇文章的大神們能夠給予指導,探究出現這種情況的原因,謝謝
原因找到了,32位JDK下沒有問題,只要是64位JDK都會出現這各現象
(多謝 搞幾年和程序員小董的回復)