首页 文章

如何在Java中正确停止Thread?

提问于
浏览
240

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

9 回答

  • 9

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

    使用 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() 的主要优点是你可以立即打破可中断的调用,这是你不能用标志方法做的 .

  • 269

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

    • run方法命中一个return子例程 .

    • 运行方法完成,并隐式返回 .

    您还可以外部停止线程:

    • 致电 system.exit (这会杀死整个过程)

    • 调用线程对象的 interrupt() 方法*

    • 查看该线程是否具有听起来像是可行的实现方法(如 kill()stop()

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

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

  • 3

    您应该始终通过检查 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) {
            ...
    
  • 1

    对于同步线程,我更喜欢使用 CountDownLatch ,这有助于线程等待进程完成 . 在这种情况下,worker类设置为具有给定计数的 CountDownLatch 实例 . 由于调用 countDown 方法或达到超时设置,对 await 方法的调用将阻塞,直到当前计数达到零 . 这种方法允许立即中断线程,而不必等待指定的等待时间过去:

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

    如果我们使用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

  • 5

    一些补充信息 . 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();
     }
    
  • 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;
     }
    
  • 154

    通常,线程在被中断时终止 . 那么,为什么不使用本机布尔值?尝试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- How can I kill a thread? without using stop();

相关问题