304

我需要一个解决方案来正确停止 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();方法,因为它已被弃用。

4

8 回答 8

325

使用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()是您可以立即中断可中断的呼叫,而使用标志方法则无法做到这一点。

于 2012-06-09T16:27:28.370 回答
187

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.");
        }
    }
}
于 2012-06-09T14:21:55.287 回答
29

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

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

您还可以从外部停止线程:

  • 调用system.exit(这会杀死你的整个过程)
  • 调用线程对象的interrupt()方法 *
  • 查看线程是否有一个听起来可以工作的已实现方法(如kill()stop()

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

您在 run 方法实现中看到的常见模式是 a while(boolean){},其中布尔值通常是名为的东西isRunning,它是其线程类的成员变量,它是易失的,并且通常可由其他线程通过各种 setter 方法访问,例如kill() { isRunnable=false; }. 这些子例程很好,因为它们允许线程在终止之前释放它持有的任何资源。

于 2015-04-22T21:44:29.660 回答
10

您应该始终通过检查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) {
        ...
于 2012-06-09T14:22:40.797 回答
8

通常,线程在被中断时终止。那么,为什么不使用本机布尔值呢?试试 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();

于 2018-11-27T10:31:53.440 回答
5

对于同步线程,我更喜欢使用CountDownLatch它来帮助线程等待正在执行的进程完成。在这种情况下,worker 类设置了一个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;
        }

    }
}

当你想完成另一个线程的执行时,在线程上执行 countDownCountDownLatchjoin主线程:

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.");
        } 
    }
}
于 2012-06-09T14:52:01.807 回答
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();
 }
于 2015-08-11T21:03:06.260 回答
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;
 }
于 2017-07-07T10:42:29.837 回答