2012-06-09 52 views
206

我需要一個解決方案來正確地停止Java中的線程。如何在Java中正確地停止線程?

我有IndexProcessor類實現Runnable接口:

public class IndexProcessor implements Runnable { 

    private static final Logger LOGGER = LoggerFactory.getLogger(IndexProcessor.class); 

    @Override 
    public void run() { 
     boolean run = true; 
     while (run) { 
      try { 
       LOGGER.debug("Sleeping..."); 
       Thread.sleep((long) 15000); 

       LOGGER.debug("Processing"); 
      } catch (InterruptedException e) { 
       LOGGER.error("Exception", e); 
       run = false; 
      } 
     } 

    } 
} 

而且我有ServletContextListener類,啓動和停止線程:

public class SearchEngineContextListener implements ServletContextListener { 

    private static final Logger LOGGER = LoggerFactory.getLogger(SearchEngineContextListener.class); 

    private Thread thread = null; 

    @Override 
    public void contextInitialized(ServletContextEvent event) { 
     thread = new Thread(new IndexProcessor()); 
     LOGGER.debug("Starting thread: " + thread); 
     thread.start(); 
     LOGGER.debug("Background process successfully started."); 
    } 

    @Override 
    public void contextDestroyed(ServletContextEvent event) { 
     LOGGER.debug("Stopping thread: " + thread); 
     if (thread != null) { 
      thread.interrupt(); 
      LOGGER.debug("Thread successfully stopped."); 
     } 
    } 
} 

但是當我關閉Tomcat的,我得到的異常在我的IndexProcessor類中:

2012-06-09 17:04:50,671 [Thread-3] ERROR IndexProcessor Exception 
java.lang.InterruptedException: sleep interrupted 
    at java.lang.Thread.sleep(Native Method) 
    at lt.ccl.searchengine.processor.IndexProcessor.run(IndexProcessor.java:22) 
    at java.lang.Thread.run(Unknown Source) 

我正在使用JDK 1.6。所以問題是:

如何停止線程並不拋出任何異常?

P.S.我不想使用.stop();方法,因爲它已被棄用。

+1

終止線程半路上總是會產生異常。如果這是正常行爲,那麼你可以捕獲並忽略'InterruptedException'。這是我的想法,但我也想知道標準的方式。 – nhahtdh

+0

如何使用'join()'? – Havelock

+0

我沒有經常使用線程,所以我對線程非常陌生,所以我不知道忽略異常是否是正常行爲。這就是我問的原因。 –

回答

128

IndexProcessor類中,您需要設置一個通知線程它需要終止的標誌的方法,類似於您剛纔在類範圍中使用的變量run

如果您希望停止線程,請設置此標誌並在線程上調用join()並等待它結束。

通過使用volatile變量或使用getter和setter方法來確保該標誌是線程安全的,這些方法與用作標誌的變量同步。

public class IndexProcessor implements Runnable { 

    private static final Logger LOGGER = LoggerFactory.getLogger(IndexProcessor.class); 
    private volatile boolean running = true; 

    public void terminate() { 
     running = false; 
    } 

    @Override 
    public void run() { 
     while (running) { 
      try { 
       LOGGER.debug("Sleeping..."); 
       Thread.sleep((long) 15000); 

       LOGGER.debug("Processing"); 
      } catch (InterruptedException e) { 
       LOGGER.error("Exception", e); 
       running = false; 
      } 
     } 

    } 
} 

然後在SearchEngineContextListener

public class SearchEngineContextListener implements ServletContextListener { 

    private static final Logger LOGGER = LoggerFactory.getLogger(SearchEngineContextListener.class); 

    private Thread thread = null; 
    private IndexProcessor runnable = null; 

    @Override 
    public void contextInitialized(ServletContextEvent event) { 
     runnable = new IndexProcessor(); 
     thread = new Thread(runnable); 
     LOGGER.debug("Starting thread: " + thread); 
     thread.start(); 
     LOGGER.debug("Background process successfully started."); 
    } 

    @Override 
    public void contextDestroyed(ServletContextEvent event) { 
     LOGGER.debug("Stopping thread: " + thread); 
     if (thread != null) { 
      runnable.terminate(); 
      thread.join(); 
      LOGGER.debug("Thread successfully stopped."); 
     } 
    } 
} 
+2

我已經完成了完全相同的事情,就在您看到您編輯它之前,您在答案中給出了示例。很好的答案!謝謝,現在一切正常:) –

+1

如果線程邏輯很複雜並且調用其他類的很多方法會怎麼樣?無處不在檢查布爾標誌。那麼該怎麼辦? – Soteric

+0

您必須更改代碼設計,以便通過向Runnable發送信號的方式構建該代碼,從而使線程退出。大多數用途在run方法中都有這個循環,所以通常沒有問題。 – DrYap

7

你應該總是通過檢查run()環標誌(如果有的話)結束線程。

你的線程應該是這樣的:

public class IndexProcessor implements Runnable { 

    private static final Logger LOGGER = LoggerFactory.getLogger(IndexProcessor.class); 
    private volatile boolean execute; 

    @Override 
    public void run() { 
     this.execute = true; 
     while (this.execute) { 
      try { 
       LOGGER.debug("Sleeping..."); 
       Thread.sleep((long) 15000); 

       LOGGER.debug("Processing"); 
      } catch (InterruptedException e) { 
       LOGGER.error("Exception", e); 
       this.execute = false; 
      } 
     } 
    } 

    public void stopExecuting() { 
     this.execute = false; 
    } 
} 

然後你就可以通過調用thread.stopExecuting()結束線程。這樣線程結束乾淨,但這需要15秒(由於你的睡眠)。 如果真的很緊急,你仍然可以調用thread.interrupt(),但是首選的方式應該總是檢查標誌。

爲了避免等待15秒,你可以拆分睡眠是這樣的:

 ... 
     try { 
      LOGGER.debug("Sleeping..."); 
      for (int i = 0; (i < 150) && this.execute; i++) { 
       Thread.sleep((long) 100); 
      } 

      LOGGER.debug("Processing"); 
     } catch (InterruptedException e) { 
     ... 
+2

它不是'Thread' - 它實現'Runnable' - 你不能調用'Thread'方法,除非你將它聲明爲'Thread',在這種情況下你不能調用'stopExecuting()' – mmcrae

5

爲了同步我更喜歡使用CountDownLatch這有助於線程等待,直到執行完畢的進程線程。在這種情況下,工人類將使用具有給定計數的CountDownLatch實例進行設置。對await方法的調用會阻塞,直到當前計數由於countDown方法的調用或達到超時設置而變爲零。這種方法允許立即中斷線程,而無需等待指定的等待時間流逝:

public class IndexProcessor implements Runnable { 

    private static final Logger LOGGER = LoggerFactory.getLogger(IndexProcessor.class); 

    private final CountDownLatch countdownlatch; 
    public IndexProcessor(CountDownLatch countdownlatch) { 
     this.countdownlatch = countdownlatch; 
    } 


    public void run() { 
     try { 
      while (!countdownlatch.await(15000, TimeUnit.MILLISECONDS)) { 
       LOGGER.debug("Processing..."); 
      } 
     } catch (InterruptedException e) { 
      LOGGER.error("Exception", e); 
      run = false; 
     } 

    } 
} 

當你要完成其他線程的執行,對CountDownLatchjoin執行倒計時線程主線程:

public class SearchEngineContextListener implements ServletContextListener { 

    private static final Logger LOGGER = LoggerFactory.getLogger(SearchEngineContextListener.class); 

    private Thread thread = null; 
    private IndexProcessor runnable = null; 
    private CountDownLatch countdownLatch = null; 

    @Override 
    public void contextInitialized(ServletContextEvent event) { 
     countdownLatch = new CountDownLatch(1); 
     Thread thread = new Thread(new IndexProcessor(countdownLatch)); 
     LOGGER.debug("Starting thread: " + thread); 
     thread.start(); 
     LOGGER.debug("Background process successfully started."); 
    } 

    @Override 
    public void contextDestroyed(ServletContextEvent event) { 
     LOGGER.debug("Stopping thread: " + thread); 
     if (countdownLatch != null) 
     { 
      countdownLatch.countDown(); 
     } 
     if (thread != null) { 
      try { 
       thread.join(); 
      } catch (InterruptedException e) { 
       LOGGER.error("Exception", e); 
      } 
      LOGGER.debug("Thread successfully stopped."); 
     } 
    } 
} 
241

使用Thread.interrupt()是完全可以接受的方法。事實上,如上所述,這可能是最好的一面旗幟。原因在於,如果您處於可中斷的阻止呼叫(如Thread.sleep或使用java.nio頻道操作),您實際上可以立即跳出這些呼叫。

如果您使用標誌,則必須等待阻止操作完成,然後才能檢查標誌。在某些情況下,您必須執行此操作,例如使用不可中斷的標準InputStream/OutputStream

在這種情況下,當線程中斷時,它不會中斷IO,但是,您可以輕鬆地在代碼中執行此操作(並且您應該在可以安全停止和清理的關鍵點執行此操作)

if (Thread.currentThread().isInterrupted()) { 
    // cleanup and stop execution 
    // for example a break in a loop 
} 

就像我說的,主要的優勢是Thread.interrupt()可以立即破斷的電話,你不能用國旗做的辦法出來。

+19

+1 - Thread.interupt()*絕對比用ad-hoc標誌實現同樣的東西更可取。 –

+1

我也認爲這是完美而有效的方式。 +1 – RoboAlex

+4

代碼中存在一個小的錯字,Thread.currentThread()沒有括號。 –

18

答案很簡單: 您可以通過以下兩種常見的方式一個INTERNALLY停止線程:

  • run方法打回子程序。
  • 運行方法結束,並隱式返回。

您也可以停止線程EXTERNALLY:

  • 呼叫system.exit(這殺死你的整個過程)
  • 呼叫
  • 線程對象的interrupt()方法*請參閱如果線程的實現方法聽起來像它會工作(如kill()stop()

*:期望的是,這應該阻止一個線程。但是,線程在發生這種情況時的實際作用完全取決於開發人員在創建線程實現時編寫的內容。

你看到一個常見的模式與運行方法的實現是一個while(boolean){},在布爾邏輯通常是一些名爲isRunning,這是它的線程類的成員變量,它的揮發,並通過各種各樣的setter方法通常是通過其他線程訪問,例如kill() { isRunnable=false; }。這些子程序很好,因爲它們允許線程在終止之前釋放它保存的任何資源。

2

一些補充信息。 在Java文檔中建議使用標誌和中斷。

https://docs.oracle.com/javase/8/docs/technotes/guides/concurrency/threadPrimitiveDeprecation.html

private volatile Thread blinker; 

public void stop() { 
    blinker = null; 
} 

public void run() { 
    Thread thisThread = Thread.currentThread(); 
    while (blinker == thisThread) { 
     try { 
      Thread.sleep(interval); 
     } catch (InterruptedException e){ 
     } 
     repaint(); 
    } 
} 

對於等待很長時間(例如,用於輸入)一個線程,使用Thread.interrupt

public void stop() { 
    Thread moribund = waiter; 
     waiter = null; 
     moribund.interrupt(); 
} 
3

我沒有中斷在Android的工作,所以我使用這種方法,完美的作品:

boolean shouldCheckUpdates = true; 

private void startupCheckForUpdatesEveryFewSeconds() { 
    threadCheckChat = new Thread(new CheckUpdates()); 
    threadCheckChat.start(); 
} 

private class CheckUpdates implements Runnable{ 
    public void run() { 
     while (shouldCheckUpdates){ 
      System.out.println("Do your thing here"); 
     } 
    } 
} 

public void stop(){ 
     shouldCheckUpdates = false; 
} 
3

如果我們使用的是JDK 1.0,我們可以調用Thread的不推薦使用的方法stop()來終止它。使用stop()是非常危險的,因爲它會殺死你的線程,即使它處於重要的事情中。沒有辦法保護你自己,所以如果你發現使用stop()的代碼,你應該皺眉頭。

我們如何徹底關閉線程?在Java中,啓動線程很容易,但關閉它們需要大量的關注和努力。

這是它是如何在Java中設計的。在每個Java線程中都有一個叫做中斷狀態標誌的標誌,我們可以從外部設置它,也就是父主線程或主線程。線程可能偶爾檢查並停止執行。自願.. !!這裏是如何:

Thread loop = new Thread(new Runnable() { 
@Override 
public void run() { 
    while (true) { 
    if (Thread.interrupted()) { 
     break; 
    } 
    // Continue to do nothing 
    } 
}}); loop.start(); loop.interrupt(); 

來源:How to stop thread in java | MultiThreading