如何在Java中正确停止Thread?

Pau*_*nis 259 java multithreading listener

我需要一个解决方案来正确地停止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;
            }
        }

    }
}
Run Code Online (Sandbox Code Playgroud)

我有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.");
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

但是当我关闭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)
Run Code Online (Sandbox Code Playgroud)

我使用的是JDK 1.6.所以问题是:

如何停止线程而不抛出任何异常?

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

Mat*_*att 288

使用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
}
Run Code Online (Sandbox Code Playgroud)

就像我说的那样,主要优点Thread.interrupt()是你可以立即打破可中断的调用,这是你不能用标志方法做的.

  • +1 - Thread.interupt()绝对*优于使用ad-hoc标志实现相同的东西. (31认同)
  • 代码中有一个小错字,Thread.currentThread()没有括号. (4认同)
  • 我也认为这是完美而有效的方式.+1 (2认同)
  • 实际上,使用标志并不可取,因为与线程接触的其他人可能会从其他地方中断它,导致它停止并且很难调试。始终使用标志。 (2认同)
  • 在这种特定情况下,调用“interrupt()”可能没问题,但在许多其他情况下则不然(例如,如果需要关闭资源)。如果有人更改了循环的内部工作方式,您必须记住将“interrupt()”更改为布尔方式。我会从一开始就选择安全的方式并使用标志。 (2认同)

DrY*_*Yap 166

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;
            }
        }

    }
}
Run Code Online (Sandbox Code Playgroud)

然后在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.");
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 倾向于传播糟糕的建议.手动标记方法意味着应用程序必须等待睡眠完成,中断会缩短睡眠时间.修改它以使用Thread#interrupt很容易. (13认同)
  • 在我看到你编辑它之前,我和你在答案中给出的例子完全相同.很棒的答案!谢谢,现在一切正常:) (3认同)
  • 如果join()语句抛出InterruptedException会发生什么? (3认同)

小智 25

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

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

您还可以外部停止线程:

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

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

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

  • "这些子程序很好,因为它们允许线程在终止之前释放它所拥有的任何资源." 我不明白.您可以使用"官方"中断状态完美地清​​理线程所拥有的资源.只需使用Thread.currentThread().isInterrupted()或Thread.interrupted()(根据需要)检查它,或者捕获InterruptedException和cleanup.哪里出了问题? (3认同)

Chr*_*ris 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;
    }
}
Run Code Online (Sandbox Code Playgroud)

然后你可以通过调用结束线程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) {
        ...
Run Code Online (Sandbox Code Playgroud)

  • 它不是一个`Thread` - 它实现了`Runnable` - 你不能在它上面调用`Thread`方法,除非你把它声明为`Thread`,在这种情况下你不能调用`stopExecuting()` (2认同)

Lov*_*rma 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();
Run Code Online (Sandbox Code Playgroud)

ref-我怎样才能杀死一个线程?不使用 stop();


Edu*_*Ros 5

对于同步我更喜欢使用的CountDownLatch线程,这有助于线程等待直到执行完成的进程.在这种情况下,将CountDownLatch使用具有给定计数的实例设置worker类.到的呼叫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;
        }

    }
}
Run Code Online (Sandbox Code Playgroud)

当你要完成其他线程的执行,执行进入倒计时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.");
        } 
    }
}
Run Code Online (Sandbox Code Playgroud)