2012-06-09 54 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