2017-01-03 158 views
6

我只是偶然發現了一個我不能解釋的守護線程奇怪的行爲。我已經減少了我的代碼,以最小的,完整的和可覈查樣本:JVM不會退出,直到守護進程線程未完成

public static void main(String[] args) throws InterruptedException { 

    Thread runner = new Thread(() -> { 

     final int SIZE = 350_000; 
     for (int i = 0; i < SIZE; i++) { 
      for (int j = i + 1; j < SIZE; j++) { 
       if (i*j == SIZE * SIZE - 1) { 
        return; 
       } 
      } 
     } 
    }); 

    runner.setDaemon(true); 
    runner.start(); 

    // Thread.sleep(1000); 
    System.out.println("Exiting."); 
} 

runner線程執行的代碼需要大約12秒至終止我的盒子,我們不感興趣,它做什麼,因爲我只需要花一些時間計算。

如果運行這段代碼,因爲它是,它按預期工作:它只是它的開始後終止。 如果我取消註釋Thread.sleep(1000)行並運行該程序,它將運行約12秒鐘,然後打印出「退出」並終止。

據我瞭解守護線程如何工作,我預計此代碼運行1秒,然後終止執行,因爲唯一的用戶線程運行是與main()方法啓動的(runner是一個後臺守護進程線程),只要1000毫秒通過,它就會到達執行結束,並且JVM應該停止。此外,看起來很奇怪,「退出」僅在12秒後打印,而不是在程序啓動時打印。

我錯了嗎?我如何實現所需的行爲(暫停一秒鐘,然後停止,與跑步者線程無關)?

我使用的是Linux中一個64位的Oracle JDK 1.8.0_112,它具有相同的行爲或者如果來自一個IDE或命令行啓動。

謝謝, 安德烈

+0

這_definitely_聽起來很熟悉,但我不太記得了足夠的找到搜索詞,不僅將讓我對了Thread.sleep和守護程序線程背景信息氾濫。 : - \ – yshavit

+0

FWIW,這個工作方式與你在我的盒子上的預期一樣(windows,jdk 1.8.0_31)。你的盒子是多CPU嗎? – user2189998

+2

如果需要,調度程序可以決定在繼續執行主線程之前運行守護線程12秒。沒有任何事物禁止你觀察到的行爲。 –

回答

5

這也許是計算循環優化的結果可以除去還原點投票從您的嵌套循環。嘗試將-XX:+UseCountedLoopSafepoint標誌添加到您的JVM啓動選項。

+0

它可以像預期的那樣工作,謝謝! –

2

Thread#sleep(long)暫停主線程它從它的主要方法返回之前(即,JVM正在考慮只要沒有非守護進程線程是活着完成該程序之前)。調度程序可以自由運行任何其他可運行的線程,這將是deamon線程。就目前而言,JVM沒有明顯的理由強制搶佔deamon線程,然後在主線程中繼續執行以繼續執行(因爲它已完成睡眠),所以JVM可以自由地繼續其日程安排。但是,它可能在任何時候選擇暫停正在運行的線程並安排另一個可運行的線程執行,因此無法保證您的示例具有可重複性。

您可以通過在循環中插入調用Thread#yield()#sleep(1)來強制搶佔。我敢打賭,你會開始看到這個片段更快地退出,並在它完成循環之前。

有更多瞭解線程狀態和調度,一個很好的概述,可以發現here

更新發表評論:

我不能修改後臺線程的代碼(是要求),所以我一直在尋找一種方法來阻止它,如果它的時間太長(的說明我在做什麼是stackoverflow.com/questions/41226054/...)。

這是法律上只可能stop a running thread from within,所以通常把它考到放棄狀況每次迭代,並且如果條件滿足時,run方法return;秒。中止條件可以像從外部設置的布爾標誌一樣簡單(!volatile caveat!)。所以最簡單的解決方案就是讓主線程在睡眠之後設置一個這樣的標誌。

另一種可能性是使用支持超時的ExecutorService,有關涉及ScheduledExecutorService的示例,請參閱this Q & A.

但我不明白調度器如何決定在運行System.out指令之前等待12秒。

它不等待 12秒,這讓我們的守護進程線程運行完,因爲在決定是否安全停止JVM時是一個守護進程僅事項向JVM。對於調度,僅螺紋事務的狀態,只要它concernced,主線程的1秒入睡後,它有一個運行(守護進程)和一個可運行的線程(主),並沒有跡象顯示正在運行的線程應該暫停以支持可運行線程。切換線程在計算上也很昂貴,所以調度器可能不願意缺少任何指示。一個指示切換可能會睡覺和收益,但也運行GC和其他很多事情。

+0

我不能修改後臺線程的代碼(是要求),所以我一直在尋找一種方式,如果阻止它它需要很長時間(我正在做什麼的描述是https://stackoverflow.com/questions/41226054/killing-a-java-thread-in-test)。仍然我不明白如何調度程序可以決定在運行System.out指令之前等待12秒.. –

+0

@AndreaIacono我相應地更新了我的答案。這是否清理了事情? – hiergiltdiestfu

+0

使用ExecutorService具有相同的行爲,我已經嘗試過。等待12秒鐘:我已經使用了多年線程,並且通常調度程序會在幾毫秒或幾十毫秒後切換它們;在這種情況下,交換機會比平常晚1000多次,這對我來說似乎很陌生。事實上,我剛剛嘗試過使用JDK 7和6,但這種情況不會發生:系統。out在sleep()之後正確打印,而不是在runner線程結束時打印。但即使使用這些JDK,守護程序線程也會在其執行過程中保持活動狀態,即使用戶線程已終止。 –