如何正确停止Java中的线程?


276

我需要一个解决方案来正确停止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。所以问题是:

如何停止线程并且不引发任何异常?

PS我不想使用.stop();方法,因为它已被弃用。


1
中途终止线程将始终生成异常。如果是正常行为,则可以捕获并忽略InterruptedException。这就是我的想法,但我也想知道标准方法是怎样的。
nhahtdh 2012年

我没有经常使用线程,因此我在线程方面还很陌生,所以我不知道忽略异常是否是正常行为。这就是为什么我要问。
Paulius Matulionis,2012年

在许多情况下,忽略异常并终止方法处理是正常的行为。请参阅下面的答案,以了解为什么它比基于标志的方法更好。
马特

1
InterruptedException可以在ibm.com/developerworks/library/j-jtp05236中找到关于B. Goetz的详尽解释。
丹尼尔(Daniel)

InterruptedException并不是问题,您在发布的代码中唯一的问题就是您不应该将其记录为错误,除了作为调试目的只是为了演示它是否在您感兴趣的情况下进行记录之外,确实没有令人信服的理由将其记录为全部。所选的答案很不幸,因为它不允许将简短的通话减少到睡眠和等待之类的时间。
内森·休斯

Answers:


173

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.");
        }
    }
}

3
我所做的与您在回答中给出的示例完全相同,就在我看过您对其进行编辑之前。好答案!谢谢,现在一切正常:)
Paulius Matulionis 2012年

1
如果线程逻辑很复杂并调用许多其他类的方法怎么办?不可能到处检查布尔标志。那该怎么办呢?
Soteric 2012年

您将不得不更改代码设计,以便以一种向Runnable发出信号的方式导致线程退出的方式构建代码。大多数用途在run方法中确实存在此循环,因此通常不会出现问题。
DrYap 2012年

3
如果join()语句抛出InterruptedException,会发生什么?
benzaita 2014年

14
因传播不良建议而被否决。手动滚动标志方法意味着应用程序必须等待睡眠完成,否则中断将缩短睡眠时间。修改它以使用Thread#interrupt很容易。
内森·休斯

298

使用Thread.interrupt()是一种完全可以接受的方式。实际上,它可能比上面建议的标志更可取。原因是,如果您处于可中断的阻塞调用(例如Thread.sleep或使用java.nio Channel操作),则实际上可以立即摆脱这些干扰。

如果使用标志,则必须等待阻止操作完成,然后才能检查标志。在某些情况下,您仍然必须执行此操作,例如使用标准的InputStream/ OutputStream不可中断。

在这种情况下,当线程被中断时,它不会中断IO,但是,您可以轻松地在代码中常规执行此操作(并且应该在可以安全地停止和清理的关键时刻执行此操作)

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

就像我说的那样,它的主要优点Thread.interrupt()是您可以立即中断可中断的调用,而使用标志方法是无法做到的。


32
+1-Thread.interupt()绝对比使用ad-hoc标志实现相同的东西更好。
斯蒂芬·C

2
我也认为这是一种完美而有效的方法。+1
RoboAlex

4
代码中有一个小的错字,Thread.currentThread()没有括号。
Vlad V

1
实际上,最好不要使用标志,因为与线程接触的其他人可能会在其他地方对其进行中断,从而导致该线程停止并且很难调试。始终也使用标志。
JohnyTex

在这种特定情况下,调用interrupt()是可以的,但在很多其他情况下,调用不是可以的(例如,如果需要关闭资源)。如果有人更改了循环的内部工作方式,则必须记住要更改interrupt()为布尔方式。我会从一开始就寻求安全的方法并使用该标志。
m0skit0 2016年

25

简单的答案:您可以通过以下两种常见方式之一在内部停止线程:

  • run方法命中一个return子例程。
  • 运行方法完成,并隐式返回。

您还可以外部停止线程:

  • 致电system.exit(这会杀死您的整个过程)
  • 调用线程对象的interrupt()方法*
  • 查看线程是否具有听起来像它将起作用的已实现方法(如kill()stop()

*:期望这应该停止线程。但是,线程在发生这种情况时实际执行的操作完全取决于开发人员在创建线程实现时编写的内容。

在run方法实现中while(boolean){},您会看到一个常见的模式是,其中boolean通常是一个名为的名称isRunning,它是其线程类的成员变量,是易失的,并且通常可由其他线程通过setter方法(例如)进行访问kill() { isRunnable=false; }。这些子例程很不错,因为它们允许线程在终止之前释放其持有的所有资源。


3
“这些子例程很好,因为它们允许线程在终止之前释放其拥有的任何资源。” 我不明白 您可以使用“正式”中断状态来完美地清理线程的资源。只需使用Thread.currentThread()。isInterrupted()或Thread.interrupted()(适合您的需要)对其进行检查,或者捕获InterruptedException,然后进行清理。哪里出问题了?
Franz D.

我无法理解flag方法为何起作用,因为我还不知道运行命中返回时它会停止!!!这太简单了,亲爱的先生,谢谢你指出这一点,没有人明确地做到这一点。
thahgr

9

您应该始终通过检查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在其上调用方法,除非您声明它为ThreadstopExecuting()
in-

7

通常,线程在中断时会终止。那么,为什么不使用本地布尔值呢?尝试isInterrupted():

Thread t = new Thread(new Runnable(){
        @Override
        public void run() {
            while(!Thread.currentThread().isInterrupted()){
                // do stuff         
            }   
        }});
    t.start();

    // Sleep a second, and then interrupt
    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {}
    t.interrupt();

ref- 如何杀死线程?不使用stop();


5

为了同步线程,我更喜欢使用CountDownLatchwhich来帮助线程等到执行完毕。在这种情况下,将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主线程上执行countDown :

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.");
        } 
    }
}

3

一些补充信息。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
永远不要忽略InterruptedException。这意味着其他一些代码明确要求您的线程终止。忽略该请求的线程是流氓线程。处理InterruptedException的正确方法是退出循环。
VGR

2

我没有在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;
 }

这很可能会失败,因为shouldCheckUpdates不是volatile。请参阅docs.oracle.com/javase/specs/jls/se9/html/jls-17.html#jls-17.3
VGR

0

有时我会在onDestroy()/ contextDestroyed()中尝试1000次

      @Override
    protected void onDestroy() {
        boolean retry = true;
        int counter = 0;
        while(retry && counter<1000)
        {
            counter++;
            try{thread.setRunnung(false);
                thread.join();
                retry = false;
                thread = null; //garbage can coll
            }catch(InterruptedException e){e.printStackTrace();}
        }

    }
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.